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