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