1 /* 2 * Copyright (C) 2019 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.gestures; 18 19 import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; 20 import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS; 21 import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT; 22 23 import android.content.Context; 24 import android.graphics.Point; 25 import android.util.Slog; 26 import android.view.MotionEvent; 27 import android.view.MotionEvent.PointerCoords; 28 import android.view.MotionEvent.PointerProperties; 29 import android.view.accessibility.AccessibilityEvent; 30 import android.view.accessibility.AccessibilityManager; 31 32 import com.android.server.accessibility.AccessibilityManagerService; 33 import com.android.server.accessibility.EventStreamTransformation; 34 import com.android.server.policy.WindowManagerPolicy; 35 36 /** 37 * This class dispatches motion events and accessibility events relating to touch exploration and 38 * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is 39 * set correctly so that events go to the right place. 40 */ 41 class EventDispatcher { 42 private static final String LOG_TAG = "EventDispatcher"; 43 private static final int CLICK_LOCATION_NONE = 0; 44 private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1; 45 private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2; 46 47 private final AccessibilityManagerService mAms; 48 private Context mContext; 49 // The receiver of motion events. 50 private EventStreamTransformation mReceiver; 51 52 // The long pressing pointer id if coordinate remapping is needed for double tap and hold 53 private int mLongPressingPointerId = -1; 54 55 // The long pressing pointer X if coordinate remapping is needed for double tap and hold. 56 private int mLongPressingPointerDeltaX; 57 58 // The long pressing pointer Y if coordinate remapping is needed for double tap and hold. 59 private int mLongPressingPointerDeltaY; 60 61 // Temporary point to avoid instantiation. 62 private final Point mTempPoint = new Point(); 63 64 private TouchState mState; 65 EventDispatcher( Context context, AccessibilityManagerService ams, EventStreamTransformation receiver, TouchState state)66 EventDispatcher( 67 Context context, 68 AccessibilityManagerService ams, 69 EventStreamTransformation receiver, 70 TouchState state) { 71 mContext = context; 72 mAms = ams; 73 mReceiver = receiver; 74 mState = state; 75 } 76 setReceiver(EventStreamTransformation receiver)77 public void setReceiver(EventStreamTransformation receiver) { 78 mReceiver = receiver; 79 } 80 81 /** 82 * Sends an event. 83 * 84 * @param prototype The prototype from which to create the injected events. 85 * @param action The action of the event. 86 * @param rawEvent The original event prior to magnification or other transformations. 87 * @param pointerIdBits The bits of the pointers to send. 88 * @param policyFlags The policy flags associated with the event. 89 */ sendMotionEvent( MotionEvent prototype, int action, MotionEvent rawEvent, int pointerIdBits, int policyFlags)90 void sendMotionEvent( 91 MotionEvent prototype, 92 int action, 93 MotionEvent rawEvent, 94 int pointerIdBits, 95 int policyFlags) { 96 prototype.setAction(action); 97 98 MotionEvent event = null; 99 if (pointerIdBits == ALL_POINTER_ID_BITS) { 100 event = prototype; 101 } else { 102 try { 103 event = prototype.split(pointerIdBits); 104 } catch (IllegalArgumentException e) { 105 Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e); 106 return; 107 } 108 } 109 if (action == MotionEvent.ACTION_DOWN) { 110 event.setDownTime(event.getEventTime()); 111 } else { 112 event.setDownTime(mState.getLastInjectedDownEventTime()); 113 } 114 // If the user is long pressing but the long pressing pointer 115 // was not exactly over the accessibility focused item we need 116 // to remap the location of that pointer so the user does not 117 // have to explicitly touch explore something to be able to 118 // long press it, or even worse to avoid the user long pressing 119 // on the wrong item since click and long press behave differently. 120 if (mLongPressingPointerId >= 0) { 121 event = offsetEvent(event, -mLongPressingPointerDeltaX, -mLongPressingPointerDeltaY); 122 } 123 124 if (DEBUG) { 125 Slog.d( 126 LOG_TAG, 127 "Injecting event: " 128 + event 129 + ", policyFlags=0x" 130 + Integer.toHexString(policyFlags)); 131 } 132 133 // Make sure that the user will see the event. 134 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; 135 if (mReceiver != null) { 136 mReceiver.onMotionEvent(event, rawEvent, policyFlags); 137 } else { 138 Slog.e(LOG_TAG, "Error sending event: no receiver specified."); 139 } 140 mState.onInjectedMotionEvent(event); 141 142 if (event != prototype) { 143 event.recycle(); 144 } 145 } 146 147 /** 148 * Sends an accessibility event of the given type. 149 * 150 * @param type The event type. 151 */ sendAccessibilityEvent(int type)152 void sendAccessibilityEvent(int type) { 153 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); 154 if (accessibilityManager.isEnabled()) { 155 AccessibilityEvent event = AccessibilityEvent.obtain(type); 156 event.setWindowId(mAms.getActiveWindowId()); 157 accessibilityManager.sendAccessibilityEvent(event); 158 if (DEBUG) { 159 Slog.d( 160 LOG_TAG, 161 "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type)); 162 } 163 } 164 // Todo: get rid of this and have TouchState control the sending of events rather than react 165 // to it. 166 mState.onInjectedAccessibilityEvent(type); 167 } 168 169 @Override toString()170 public String toString() { 171 StringBuilder builder = new StringBuilder(); 172 builder.append("========================="); 173 builder.append("\nDown pointers #"); 174 builder.append(Integer.bitCount(mState.getInjectedPointersDown())); 175 builder.append(" [ "); 176 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 177 if (mState.isInjectedPointerDown(i)) { 178 builder.append(i); 179 builder.append(" "); 180 } 181 } 182 builder.append("]"); 183 builder.append("\n========================="); 184 return builder.toString(); 185 } 186 187 /** 188 * /** Offsets all pointers in the given event by adding the specified X and Y offsets. 189 * 190 * @param event The event to offset. 191 * @param offsetX The X offset. 192 * @param offsetY The Y offset. 193 * @return An event with the offset pointers or the original event if both offsets are zero. 194 */ offsetEvent(MotionEvent event, int offsetX, int offsetY)195 private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { 196 if (offsetX == 0 && offsetY == 0) { 197 return event; 198 } 199 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); 200 final int pointerCount = event.getPointerCount(); 201 PointerProperties[] props = PointerProperties.createArray(pointerCount); 202 PointerCoords[] coords = PointerCoords.createArray(pointerCount); 203 for (int i = 0; i < pointerCount; i++) { 204 event.getPointerProperties(i, props[i]); 205 event.getPointerCoords(i, coords[i]); 206 if (i == remappedIndex) { 207 coords[i].x += offsetX; 208 coords[i].y += offsetY; 209 } 210 } 211 return MotionEvent.obtain( 212 event.getDownTime(), 213 event.getEventTime(), 214 event.getAction(), 215 event.getPointerCount(), 216 props, 217 coords, 218 event.getMetaState(), 219 event.getButtonState(), 220 1.0f, 221 1.0f, 222 event.getDeviceId(), 223 event.getEdgeFlags(), 224 event.getSource(), 225 event.getDisplayId(), 226 event.getFlags()); 227 } 228 229 /** 230 * Computes the action for an injected event based on a masked action and a pointer index. 231 * 232 * @param actionMasked The masked action. 233 * @param pointerIndex The index of the pointer which has changed. 234 * @return The action to be used for injection. 235 */ computeInjectionAction(int actionMasked, int pointerIndex)236 private int computeInjectionAction(int actionMasked, int pointerIndex) { 237 switch (actionMasked) { 238 case MotionEvent.ACTION_DOWN: 239 case MotionEvent.ACTION_POINTER_DOWN: 240 // Compute the action based on how many down pointers are injected. 241 if (mState.getInjectedPointerDownCount() == 0) { 242 return MotionEvent.ACTION_DOWN; 243 } else { 244 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 245 | MotionEvent.ACTION_POINTER_DOWN; 246 } 247 case MotionEvent.ACTION_POINTER_UP: 248 // Compute the action based on how many down pointers are injected. 249 if (mState.getInjectedPointerDownCount() == 1) { 250 return MotionEvent.ACTION_UP; 251 } else { 252 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 253 | MotionEvent.ACTION_POINTER_UP; 254 } 255 default: 256 return actionMasked; 257 } 258 } 259 260 /** 261 * Sends down events to the view hierarchy for all pointers which are not already being 262 * delivered i.e. pointers that are not yet injected. 263 * 264 * @param prototype The prototype from which to create the injected events. 265 * @param policyFlags The policy flags associated with the event. 266 */ sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags)267 void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { 268 269 // Inject the injected pointers. 270 int pointerIdBits = 0; 271 final int pointerCount = prototype.getPointerCount(); 272 for (int i = 0; i < pointerCount; i++) { 273 final int pointerId = prototype.getPointerId(i); 274 // Do not send event for already delivered pointers. 275 if (!mState.isInjectedPointerDown(pointerId)) { 276 pointerIdBits |= (1 << pointerId); 277 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); 278 sendMotionEvent( 279 prototype, 280 action, 281 mState.getLastReceivedEvent(), 282 pointerIdBits, 283 policyFlags); 284 } 285 } 286 } 287 288 /** 289 * Sends down events to the view hierarchy for all pointers which are not already being 290 * delivered with original down location. i.e. pointers that are not yet injected. The down time 291 * is also replaced by the original one. 292 * 293 * 294 * @param prototype The prototype from which to create the injected events. 295 * @param policyFlags The policy flags associated with the event. 296 */ sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags)297 void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) { 298 // Inject the injected pointers. 299 int pointerIdBits = 0; 300 final int pointerCount = prototype.getPointerCount(); 301 final MotionEvent event = computeInjectionDownEvent(prototype); 302 for (int i = 0; i < pointerCount; i++) { 303 final int pointerId = prototype.getPointerId(i); 304 // Do not send event for already delivered pointers. 305 if (!mState.isInjectedPointerDown(pointerId)) { 306 pointerIdBits |= (1 << pointerId); 307 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); 308 sendMotionEvent( 309 event, 310 action, 311 mState.getLastReceivedEvent(), 312 pointerIdBits, 313 policyFlags); 314 } 315 } 316 } 317 computeInjectionDownEvent(MotionEvent prototype)318 private MotionEvent computeInjectionDownEvent(MotionEvent prototype) { 319 final int pointerCount = prototype.getPointerCount(); 320 if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) { 321 Slog.w(LOG_TAG, "The pointer count doesn't match the received count."); 322 return MotionEvent.obtain(prototype); 323 } 324 MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount]; 325 MotionEvent.PointerProperties[] properties = 326 new MotionEvent.PointerProperties[pointerCount]; 327 for (int i = 0; i < pointerCount; ++i) { 328 final int pointerId = prototype.getPointerId(i); 329 final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId); 330 final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId); 331 coords[i] = new MotionEvent.PointerCoords(); 332 coords[i].x = x; 333 coords[i].y = y; 334 properties[i] = new MotionEvent.PointerProperties(); 335 properties[i].id = pointerId; 336 properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER; 337 } 338 MotionEvent event = 339 MotionEvent.obtain( 340 prototype.getDownTime(), 341 // The event time is used for downTime while sending ACTION_DOWN. We adjust 342 // it to avoid the motion velocity is too fast in the beginning after 343 // Delegating. 344 prototype.getDownTime(), 345 prototype.getAction(), 346 pointerCount, 347 properties, 348 coords, 349 prototype.getMetaState(), 350 prototype.getButtonState(), 351 prototype.getXPrecision(), 352 prototype.getYPrecision(), 353 prototype.getDeviceId(), 354 prototype.getEdgeFlags(), 355 prototype.getSource(), 356 prototype.getFlags()); 357 return event; 358 } 359 360 /** 361 * 362 * Sends up events to the view hierarchy for all pointers which are already being delivered i.e. 363 * pointers that are injected. 364 * 365 * @param prototype The prototype from which to create the injected events. 366 * @param policyFlags The policy flags associated with the event. 367 */ sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags)368 void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { 369 int pointerIdBits = prototype.getPointerIdBits(); 370 final int pointerCount = prototype.getPointerCount(); 371 for (int i = 0; i < pointerCount; i++) { 372 final int pointerId = prototype.getPointerId(i); 373 // Skip non injected down pointers. 374 if (!mState.isInjectedPointerDown(pointerId)) { 375 continue; 376 } 377 final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i); 378 sendMotionEvent( 379 prototype, action, mState.getLastReceivedEvent(), pointerIdBits, policyFlags); 380 pointerIdBits &= ~(1 << pointerId); 381 } 382 } 383 longPressWithTouchEvents(MotionEvent event, int policyFlags)384 public boolean longPressWithTouchEvents(MotionEvent event, int policyFlags) { 385 final int pointerIndex = event.getActionIndex(); 386 final int pointerId = event.getPointerId(pointerIndex); 387 Point clickLocation = mTempPoint; 388 final int result = computeClickLocation(clickLocation); 389 if (result == CLICK_LOCATION_NONE) { 390 return false; 391 } 392 mLongPressingPointerId = pointerId; 393 mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x; 394 mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y; 395 sendDownForAllNotInjectedPointers(event, policyFlags); 396 return true; 397 } 398 clear()399 void clear() { 400 mLongPressingPointerId = -1; 401 mLongPressingPointerDeltaX = 0; 402 mLongPressingPointerDeltaY = 0; 403 } 404 clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags)405 public void clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 406 final int pointerIndex = event.getActionIndex(); 407 final int pointerId = event.getPointerId(pointerIndex); 408 Point clickLocation = mTempPoint; 409 final int result = computeClickLocation(clickLocation); 410 if (result == CLICK_LOCATION_NONE) { 411 Slog.e(LOG_TAG, "Unable to compute click location."); 412 // We can't send a click to no location, but the gesture was still 413 // consumed. 414 return; 415 } 416 // Do the click. 417 PointerProperties[] properties = new PointerProperties[1]; 418 properties[0] = new PointerProperties(); 419 event.getPointerProperties(pointerIndex, properties[0]); 420 PointerCoords[] coords = new PointerCoords[1]; 421 coords[0] = new PointerCoords(); 422 coords[0].x = clickLocation.x; 423 coords[0].y = clickLocation.y; 424 MotionEvent clickEvent = 425 MotionEvent.obtain( 426 event.getDownTime(), 427 event.getEventTime(), 428 MotionEvent.ACTION_DOWN, 429 1, 430 properties, 431 coords, 432 0, 433 0, 434 1.0f, 435 1.0f, 436 event.getDeviceId(), 437 0, 438 event.getSource(), 439 event.getDisplayId(), 440 event.getFlags()); 441 final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS); 442 sendActionDownAndUp(clickEvent, rawEvent, policyFlags, targetAccessibilityFocus); 443 clickEvent.recycle(); 444 } 445 computeClickLocation(Point outLocation)446 private int computeClickLocation(Point outLocation) { 447 if (mState.getLastInjectedHoverEventForClick() != null) { 448 final int lastExplorePointerIndex = 449 mState.getLastInjectedHoverEventForClick().getActionIndex(); 450 outLocation.x = 451 (int) mState.getLastInjectedHoverEventForClick().getX(lastExplorePointerIndex); 452 outLocation.y = 453 (int) mState.getLastInjectedHoverEventForClick().getY(lastExplorePointerIndex); 454 if (!mAms.accessibilityFocusOnlyInActiveWindow() 455 || mState.getLastTouchedWindowId() == mAms.getActiveWindowId()) { 456 if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { 457 return CLICK_LOCATION_ACCESSIBILITY_FOCUS; 458 } else { 459 return CLICK_LOCATION_LAST_TOUCH_EXPLORED; 460 } 461 } 462 } 463 if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { 464 return CLICK_LOCATION_ACCESSIBILITY_FOCUS; 465 } 466 return CLICK_LOCATION_NONE; 467 } 468 sendActionDownAndUp( MotionEvent prototype, MotionEvent rawEvent, int policyFlags, boolean targetAccessibilityFocus)469 private void sendActionDownAndUp( 470 MotionEvent prototype, 471 MotionEvent rawEvent, 472 int policyFlags, 473 boolean targetAccessibilityFocus) { 474 // Tap with the pointer that last explored. 475 final int pointerId = prototype.getPointerId(prototype.getActionIndex()); 476 final int pointerIdBits = (1 << pointerId); 477 prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); 478 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags); 479 prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); 480 sendMotionEvent(prototype, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags); 481 } 482 } 483