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