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 
17 package com.android.server.accessibility;
18 
19 import android.accessibilityservice.GestureDescription;
20 import android.accessibilityservice.GestureDescription.GestureStep;
21 import android.accessibilityservice.GestureDescription.TouchPoint;
22 import android.accessibilityservice.IAccessibilityServiceClient;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.RemoteException;
27 import android.os.SystemClock;
28 import android.util.IntArray;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.util.SparseIntArray;
32 import android.view.InputDevice;
33 import android.view.MotionEvent;
34 
35 import com.android.internal.os.SomeArgs;
36 import com.android.server.policy.WindowManagerPolicy;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 
42 /**
43  * Injects MotionEvents to permit {@code AccessibilityService}s to touch the screen on behalf of
44  * users.
45  * <p>
46  * All methods except {@code injectEvents} must be called only from the main thread.
47  */
48 public class MotionEventInjector extends BaseEventStreamTransformation implements Handler.Callback {
49     private static final String LOG_TAG = "MotionEventInjector";
50     private static final int MESSAGE_SEND_MOTION_EVENT = 1;
51     private static final int MESSAGE_INJECT_EVENTS = 2;
52 
53     /**
54      * Constants used to initialize all MotionEvents
55      */
56     private static final int EVENT_META_STATE = 0;
57     private static final int EVENT_BUTTON_STATE = 0;
58     private static final int EVENT_DEVICE_ID = 0;
59     private static final int EVENT_EDGE_FLAGS = 0;
60     private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
61     private static final int EVENT_FLAGS = 0;
62     private static final float EVENT_X_PRECISION = 1;
63     private static final float EVENT_Y_PRECISION = 1;
64 
65     private static MotionEvent.PointerCoords[] sPointerCoords;
66     private static MotionEvent.PointerProperties[] sPointerProps;
67 
68     private final Handler mHandler;
69     private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
70 
71     private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
72     private IntArray mSequencesInProgress = new IntArray(5);
73     private boolean mIsDestroyed = false;
74     private TouchPoint[] mLastTouchPoints;
75     private int mNumLastTouchPoints;
76     private long mDownTime;
77     private long mLastScheduledEventTime;
78     private SparseIntArray mStrokeIdToPointerId = new SparseIntArray(5);
79 
80     /**
81      * @param looper A looper on the main thread to use for dispatching new events
82      */
MotionEventInjector(Looper looper)83     public MotionEventInjector(Looper looper) {
84         mHandler = new Handler(looper, this);
85     }
86 
87     /**
88      * @param handler A handler to post messages. Exposes internal state for testing only.
89      */
MotionEventInjector(Handler handler)90     public MotionEventInjector(Handler handler) {
91         mHandler = handler;
92     }
93 
94     /**
95      * Schedule a gesture for injection. The gesture is defined by a set of {@code GestureStep}s,
96      * from which {@code MotionEvent}s will be derived. All gestures currently in progress will be
97      * cancelled.
98      *
99      * @param gestureSteps The gesture steps to inject.
100      * @param serviceInterface The interface to call back with a result when the gesture is
101      * either complete or cancelled.
102      */
injectEvents(List<GestureStep> gestureSteps, IAccessibilityServiceClient serviceInterface, int sequence)103     public void injectEvents(List<GestureStep> gestureSteps,
104             IAccessibilityServiceClient serviceInterface, int sequence) {
105         SomeArgs args = SomeArgs.obtain();
106         args.arg1 = gestureSteps;
107         args.arg2 = serviceInterface;
108         args.argi1 = sequence;
109         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
110     }
111 
112     @Override
onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)113     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
114         // MotionEventInjector would cancel any injected gesture when any MotionEvent arrives.
115         // For user using an external device to control the pointer movement, it's almost
116         // impossible to perform the gestures. Any slightly unintended movement results in the
117         // cancellation of the gesture.
118         if ((event.isFromSource(InputDevice.SOURCE_MOUSE)
119                 && event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE)
120                 && mOpenGesturesInProgress.get(EVENT_SOURCE, false)) {
121             return;
122         }
123         cancelAnyPendingInjectedEvents();
124         sendMotionEventToNext(event, rawEvent, policyFlags);
125     }
126 
127     @Override
clearEvents(int inputSource)128     public void clearEvents(int inputSource) {
129         /*
130          * Reset state for motion events passing through so we won't send a cancel event for
131          * them.
132          */
133         if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
134             mOpenGesturesInProgress.put(inputSource, false);
135         }
136     }
137 
138     @Override
onDestroy()139     public void onDestroy() {
140         cancelAnyPendingInjectedEvents();
141         mIsDestroyed = true;
142     }
143 
144     @Override
handleMessage(Message message)145     public boolean handleMessage(Message message) {
146         if (message.what == MESSAGE_INJECT_EVENTS) {
147             SomeArgs args = (SomeArgs) message.obj;
148             injectEventsMainThread((List<GestureStep>) args.arg1,
149                     (IAccessibilityServiceClient) args.arg2, args.argi1);
150             args.recycle();
151             return true;
152         }
153         if (message.what != MESSAGE_SEND_MOTION_EVENT) {
154             Slog.e(LOG_TAG, "Unknown message: " + message.what);
155             return false;
156         }
157         MotionEvent motionEvent = (MotionEvent) message.obj;
158         sendMotionEventToNext(motionEvent, motionEvent, WindowManagerPolicy.FLAG_PASS_TO_USER);
159         boolean isEndOfSequence = message.arg1 != 0;
160         if (isEndOfSequence) {
161             notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
162             mSequencesInProgress.remove(0);
163         }
164         return true;
165     }
166 
injectEventsMainThread(List<GestureStep> gestureSteps, IAccessibilityServiceClient serviceInterface, int sequence)167     private void injectEventsMainThread(List<GestureStep> gestureSteps,
168             IAccessibilityServiceClient serviceInterface, int sequence) {
169         if (mIsDestroyed) {
170             try {
171                 serviceInterface.onPerformGestureResult(sequence, false);
172             } catch (RemoteException re) {
173                 Slog.e(LOG_TAG, "Error sending status with mIsDestroyed to " + serviceInterface,
174                         re);
175             }
176             return;
177         }
178 
179         if (getNext() == null) {
180             notifyService(serviceInterface, sequence, false);
181             return;
182         }
183 
184         boolean continuingGesture = newGestureTriesToContinueOldOne(gestureSteps);
185 
186         if (continuingGesture) {
187             if ((serviceInterface != mServiceInterfaceForCurrentGesture)
188                     || !prepareToContinueOldGesture(gestureSteps)) {
189                 cancelAnyPendingInjectedEvents();
190                 notifyService(serviceInterface, sequence, false);
191                 return;
192             }
193         }
194         if (!continuingGesture) {
195             cancelAnyPendingInjectedEvents();
196             // Injected gestures have been canceled, but real gestures still need cancelling
197             cancelAnyGestureInProgress(EVENT_SOURCE);
198         }
199         mServiceInterfaceForCurrentGesture = serviceInterface;
200 
201         long currentTime = SystemClock.uptimeMillis();
202         List<MotionEvent> events = getMotionEventsFromGestureSteps(gestureSteps,
203                 (mSequencesInProgress.size() == 0) ? currentTime : mLastScheduledEventTime);
204         if (events.isEmpty()) {
205             notifyService(serviceInterface, sequence, false);
206             return;
207         }
208         mSequencesInProgress.add(sequence);
209 
210         for (int i = 0; i < events.size(); i++) {
211             MotionEvent event = events.get(i);
212             int isEndOfSequence = (i == events.size() - 1) ? 1 : 0;
213             Message message = mHandler.obtainMessage(
214                     MESSAGE_SEND_MOTION_EVENT, isEndOfSequence, 0, event);
215             mLastScheduledEventTime = event.getEventTime();
216             mHandler.sendMessageDelayed(message, Math.max(0, event.getEventTime() - currentTime));
217         }
218     }
219 
newGestureTriesToContinueOldOne(List<GestureStep> gestureSteps)220     private boolean newGestureTriesToContinueOldOne(List<GestureStep> gestureSteps) {
221         if (gestureSteps.isEmpty()) {
222             return false;
223         }
224         GestureStep firstStep = gestureSteps.get(0);
225         for (int i = 0; i < firstStep.numTouchPoints; i++) {
226             if (!firstStep.touchPoints[i].mIsStartOfPath) {
227                 return true;
228             }
229         }
230         return false;
231     }
232 
233     /**
234      * A gesture can only continue a gesture if it contains intermediate points that continue
235      * each continued stroke of the last gesture, and no extra points.
236      *
237      * @param gestureSteps The steps of the new gesture
238      * @return {@code true} if the new gesture could continue the last one dispatched. {@code false}
239      * otherwise.
240      */
prepareToContinueOldGesture(List<GestureStep> gestureSteps)241     private boolean prepareToContinueOldGesture(List<GestureStep> gestureSteps) {
242         if (gestureSteps.isEmpty() || (mLastTouchPoints == null) || (mNumLastTouchPoints == 0)) {
243             return false;
244         }
245         GestureStep firstStep = gestureSteps.get(0);
246         // Make sure all of the continuing paths match up
247         int numContinuedStrokes = 0;
248         for (int i = 0; i < firstStep.numTouchPoints; i++) {
249             TouchPoint touchPoint = firstStep.touchPoints[i];
250             if (!touchPoint.mIsStartOfPath) {
251                 int continuedPointerId = mStrokeIdToPointerId
252                         .get(touchPoint.mContinuedStrokeId, -1);
253                 if (continuedPointerId == -1) {
254                     Slog.w(LOG_TAG, "Can't continue gesture due to unknown continued stroke id in "
255                             + touchPoint);
256                     return false;
257                 }
258                 mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId);
259                 int lastPointIndex = findPointByStrokeId(
260                         mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId);
261                 if (lastPointIndex < 0) {
262                     Slog.w(LOG_TAG, "Can't continue gesture due continued gesture id of "
263                             + touchPoint + " not matching any previous strokes in "
264                             + Arrays.asList(mLastTouchPoints));
265                     return false;
266                 }
267                 if (mLastTouchPoints[lastPointIndex].mIsEndOfPath
268                         || (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX)
269                         || (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) {
270                     Slog.w(LOG_TAG, "Can't continue gesture due to points mismatch between "
271                             + mLastTouchPoints[lastPointIndex] + " and " + touchPoint);
272                     return false;
273                 }
274                 // Update the last touch point to match the continuation, so the gestures will
275                 // line up
276                 mLastTouchPoints[lastPointIndex].mStrokeId = touchPoint.mStrokeId;
277             }
278             numContinuedStrokes++;
279         }
280         // Make sure we didn't miss any paths
281         for (int i = 0; i < mNumLastTouchPoints; i++) {
282             if (!mLastTouchPoints[i].mIsEndOfPath) {
283                 numContinuedStrokes--;
284             }
285         }
286         return numContinuedStrokes == 0;
287     }
288 
sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent, int policyFlags)289     private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
290             int policyFlags) {
291         if (getNext() != null) {
292             super.onMotionEvent(event, rawEvent, policyFlags);
293             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
294                 mOpenGesturesInProgress.put(event.getSource(), true);
295             }
296             if ((event.getActionMasked() == MotionEvent.ACTION_UP)
297                     || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
298                 mOpenGesturesInProgress.put(event.getSource(), false);
299             }
300         }
301     }
302 
cancelAnyGestureInProgress(int source)303     private void cancelAnyGestureInProgress(int source) {
304         if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) {
305             long now = SystemClock.uptimeMillis();
306             MotionEvent cancelEvent =
307                     obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
308             sendMotionEventToNext(cancelEvent, cancelEvent,
309                     WindowManagerPolicy.FLAG_PASS_TO_USER);
310             mOpenGesturesInProgress.put(source, false);
311         }
312     }
313 
cancelAnyPendingInjectedEvents()314     private void cancelAnyPendingInjectedEvents() {
315         if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
316             mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
317             cancelAnyGestureInProgress(EVENT_SOURCE);
318             for (int i = mSequencesInProgress.size() - 1; i >= 0; i--) {
319                 notifyService(mServiceInterfaceForCurrentGesture,
320                         mSequencesInProgress.get(i), false);
321                 mSequencesInProgress.remove(i);
322             }
323         } else if (mNumLastTouchPoints != 0) {
324             // An injected gesture is in progress and waiting for a continuation. Cancel it.
325             cancelAnyGestureInProgress(EVENT_SOURCE);
326         }
327         mNumLastTouchPoints = 0;
328         mStrokeIdToPointerId.clear();
329     }
330 
notifyService(IAccessibilityServiceClient service, int sequence, boolean success)331     private void notifyService(IAccessibilityServiceClient service, int sequence, boolean success) {
332         try {
333             service.onPerformGestureResult(sequence, success);
334         } catch (RemoteException re) {
335             Slog.e(LOG_TAG, "Error sending motion event injection status to "
336                     + mServiceInterfaceForCurrentGesture, re);
337         }
338     }
339 
getMotionEventsFromGestureSteps( List<GestureStep> steps, long startTime)340     private List<MotionEvent> getMotionEventsFromGestureSteps(
341             List<GestureStep> steps, long startTime) {
342         final List<MotionEvent> motionEvents = new ArrayList<>();
343 
344         TouchPoint[] lastTouchPoints = getLastTouchPoints();
345 
346         for (int i = 0; i < steps.size(); i++) {
347             GestureDescription.GestureStep step = steps.get(i);
348             int currentTouchPointSize = step.numTouchPoints;
349             if (currentTouchPointSize > lastTouchPoints.length) {
350                 mNumLastTouchPoints = 0;
351                 motionEvents.clear();
352                 return motionEvents;
353             }
354 
355             appendMoveEventIfNeeded(motionEvents, step.touchPoints, currentTouchPointSize,
356                     startTime + step.timeSinceGestureStart);
357             appendUpEvents(motionEvents, step.touchPoints, currentTouchPointSize,
358                     startTime + step.timeSinceGestureStart);
359             appendDownEvents(motionEvents, step.touchPoints, currentTouchPointSize,
360                     startTime + step.timeSinceGestureStart);
361         }
362         return motionEvents;
363     }
364 
getLastTouchPoints()365     private TouchPoint[] getLastTouchPoints() {
366         if (mLastTouchPoints == null) {
367             int capacity = GestureDescription.getMaxStrokeCount();
368             mLastTouchPoints = new TouchPoint[capacity];
369             for (int i = 0; i < capacity; i++) {
370                 mLastTouchPoints[i] = new GestureDescription.TouchPoint();
371             }
372         }
373         return mLastTouchPoints;
374     }
375 
appendMoveEventIfNeeded(List<MotionEvent> motionEvents, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)376     private void appendMoveEventIfNeeded(List<MotionEvent> motionEvents,
377             TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
378             /* Look for pointers that have moved */
379         boolean moveFound = false;
380         TouchPoint[] lastTouchPoints = getLastTouchPoints();
381         for (int i = 0; i < currentTouchPointsSize; i++) {
382             int lastPointsIndex = findPointByStrokeId(lastTouchPoints, mNumLastTouchPoints,
383                     currentTouchPoints[i].mStrokeId);
384             if (lastPointsIndex >= 0) {
385                 moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX)
386                         || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY);
387                 lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]);
388             }
389         }
390 
391         if (moveFound) {
392             motionEvents.add(obtainMotionEvent(mDownTime, currentTime, MotionEvent.ACTION_MOVE,
393                     lastTouchPoints, mNumLastTouchPoints));
394         }
395     }
396 
appendUpEvents(List<MotionEvent> motionEvents, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)397     private void appendUpEvents(List<MotionEvent> motionEvents,
398             TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
399         /* Look for a pointer at the end of its path */
400         TouchPoint[] lastTouchPoints = getLastTouchPoints();
401         for (int i = 0; i < currentTouchPointsSize; i++) {
402             if (currentTouchPoints[i].mIsEndOfPath) {
403                 int indexOfUpEvent = findPointByStrokeId(lastTouchPoints, mNumLastTouchPoints,
404                         currentTouchPoints[i].mStrokeId);
405                 if (indexOfUpEvent < 0) {
406                     continue; // Should not happen
407                 }
408                 int action = (mNumLastTouchPoints == 1) ? MotionEvent.ACTION_UP
409                         : MotionEvent.ACTION_POINTER_UP;
410                 action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
411                 motionEvents.add(obtainMotionEvent(mDownTime, currentTime, action,
412                         lastTouchPoints, mNumLastTouchPoints));
413                     /* Remove this point from lastTouchPoints */
414                 for (int j = indexOfUpEvent; j < mNumLastTouchPoints - 1; j++) {
415                     lastTouchPoints[j].copyFrom(mLastTouchPoints[j + 1]);
416                 }
417                 mNumLastTouchPoints--;
418                 if (mNumLastTouchPoints == 0) {
419                     mStrokeIdToPointerId.clear();
420                 }
421             }
422         }
423     }
424 
appendDownEvents(List<MotionEvent> motionEvents, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)425     private void appendDownEvents(List<MotionEvent> motionEvents,
426             TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
427         /* Look for a pointer that is just starting */
428         TouchPoint[] lastTouchPoints = getLastTouchPoints();
429         for (int i = 0; i < currentTouchPointsSize; i++) {
430             if (currentTouchPoints[i].mIsStartOfPath) {
431                 /* Add the point to last coords and use the new array to generate the event */
432                 lastTouchPoints[mNumLastTouchPoints++].copyFrom(currentTouchPoints[i]);
433                 int action = (mNumLastTouchPoints == 1) ? MotionEvent.ACTION_DOWN
434                         : MotionEvent.ACTION_POINTER_DOWN;
435                 if (action == MotionEvent.ACTION_DOWN) {
436                     mDownTime = currentTime;
437                 }
438                 action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
439                 motionEvents.add(obtainMotionEvent(mDownTime, currentTime, action,
440                         lastTouchPoints, mNumLastTouchPoints));
441             }
442         }
443     }
444 
obtainMotionEvent(long downTime, long eventTime, int action, TouchPoint[] touchPoints, int touchPointsSize)445     private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action,
446             TouchPoint[] touchPoints, int touchPointsSize) {
447         if ((sPointerCoords == null) || (sPointerCoords.length < touchPointsSize)) {
448             sPointerCoords = new MotionEvent.PointerCoords[touchPointsSize];
449             for (int i = 0; i < touchPointsSize; i++) {
450                 sPointerCoords[i] = new MotionEvent.PointerCoords();
451             }
452         }
453         if ((sPointerProps == null) || (sPointerProps.length < touchPointsSize)) {
454             sPointerProps = new MotionEvent.PointerProperties[touchPointsSize];
455             for (int i = 0; i < touchPointsSize; i++) {
456                 sPointerProps[i] = new MotionEvent.PointerProperties();
457             }
458         }
459         for (int i = 0; i < touchPointsSize; i++) {
460             int pointerId = mStrokeIdToPointerId.get(touchPoints[i].mStrokeId, -1);
461             if (pointerId == -1) {
462                 pointerId = getUnusedPointerId();
463                 mStrokeIdToPointerId.put(touchPoints[i].mStrokeId, pointerId);
464             }
465             sPointerProps[i].id = pointerId;
466             sPointerProps[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
467             sPointerCoords[i].clear();
468             sPointerCoords[i].pressure = 1.0f;
469             sPointerCoords[i].size = 1.0f;
470             sPointerCoords[i].x = touchPoints[i].mX;
471             sPointerCoords[i].y = touchPoints[i].mY;
472         }
473         return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize,
474                 sPointerProps, sPointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE,
475                 EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS,
476                 EVENT_SOURCE, EVENT_FLAGS);
477     }
478 
findPointByStrokeId(TouchPoint[] touchPoints, int touchPointsSize, int strokeId)479     private static int findPointByStrokeId(TouchPoint[] touchPoints, int touchPointsSize,
480             int strokeId) {
481         for (int i = 0; i < touchPointsSize; i++) {
482             if (touchPoints[i].mStrokeId == strokeId) {
483                 return i;
484             }
485         }
486         return -1;
487     }
getUnusedPointerId()488     private int getUnusedPointerId() {
489         int MAX_POINTER_ID = 10;
490         int pointerId = 0;
491         while (mStrokeIdToPointerId.indexOfValue(pointerId) >= 0) {
492             pointerId++;
493             if (pointerId >= MAX_POINTER_ID) {
494                 return MAX_POINTER_ID;
495             }
496         }
497         return pointerId;
498     }
499 }
500