1 /* 2 * Copyright (C) 2011 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.content.Context; 20 import android.os.Handler; 21 import android.os.PowerManager; 22 import android.util.Pools.SimplePool; 23 import android.util.Slog; 24 import android.util.SparseBooleanArray; 25 import android.view.Choreographer; 26 import android.view.InputDevice; 27 import android.view.InputEvent; 28 import android.view.InputFilter; 29 import android.view.KeyEvent; 30 import android.view.MotionEvent; 31 import android.view.WindowManagerPolicy; 32 import android.view.accessibility.AccessibilityEvent; 33 34 import com.android.server.LocalServices; 35 36 /** 37 * This class is an input filter for implementing accessibility features such 38 * as display magnification and explore by touch. 39 * 40 * NOTE: This class has to be created and poked only from the main thread. 41 */ 42 class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { 43 44 private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); 45 46 private static final boolean DEBUG = false; 47 48 /** 49 * Flag for enabling the screen magnification feature. 50 * 51 * @see #setUserAndEnabledFeatures(int, int) 52 */ 53 static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001; 54 55 /** 56 * Flag for enabling the touch exploration feature. 57 * 58 * @see #setUserAndEnabledFeatures(int, int) 59 */ 60 static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002; 61 62 /** 63 * Flag for enabling the filtering key events feature. 64 * 65 * @see #setUserAndEnabledFeatures(int, int) 66 */ 67 static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004; 68 69 /** 70 * Flag for enabling "Automatically click on mouse stop" feature. 71 * 72 * @see #setUserAndEnabledFeatures(int, int) 73 */ 74 static final int FLAG_FEATURE_AUTOCLICK = 0x00000008; 75 76 /** 77 * Flag for enabling motion event injection. 78 * 79 * @see #setUserAndEnabledFeatures(int, int) 80 */ 81 static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010; 82 83 /** 84 * Flag for enabling the feature to control the screen magnifier. If 85 * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored 86 * as the screen magnifier feature performs a super set of the work 87 * performed by this feature. 88 * 89 * @see #setUserAndEnabledFeatures(int, int) 90 */ 91 static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020; 92 93 /** 94 * Flag for enabling the feature to trigger the screen magnifier 95 * from another on-device interaction. 96 */ 97 static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040; 98 99 static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS 100 | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION 101 | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; 102 103 private final Runnable mProcessBatchedEventsRunnable = new Runnable() { 104 @Override 105 public void run() { 106 final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); 107 if (DEBUG) { 108 Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos); 109 } 110 processBatchedEvents(frameTimeNanos); 111 if (DEBUG) { 112 Slog.i(TAG, "End batch processing."); 113 } 114 if (mEventQueue != null) { 115 scheduleProcessBatchedEvents(); 116 } 117 } 118 }; 119 120 private final Context mContext; 121 122 private final PowerManager mPm; 123 124 private final AccessibilityManagerService mAms; 125 126 private final Choreographer mChoreographer; 127 128 private boolean mInstalled; 129 130 private int mUserId; 131 132 private int mEnabledFeatures; 133 134 private TouchExplorer mTouchExplorer; 135 136 private MagnificationGestureHandler mMagnificationGestureHandler; 137 138 private MotionEventInjector mMotionEventInjector; 139 140 private AutoclickController mAutoclickController; 141 142 private KeyboardInterceptor mKeyboardInterceptor; 143 144 private EventStreamTransformation mEventHandler; 145 146 private MotionEventHolder mEventQueue; 147 148 private EventStreamState mMouseStreamState; 149 150 private EventStreamState mTouchScreenStreamState; 151 152 private EventStreamState mKeyboardStreamState; 153 154 AccessibilityInputFilter(Context context, AccessibilityManagerService service) { 155 super(context.getMainLooper()); 156 mContext = context; 157 mAms = service; 158 mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 159 mChoreographer = Choreographer.getInstance(); 160 } 161 162 @Override 163 public void onInstalled() { 164 if (DEBUG) { 165 Slog.d(TAG, "Accessibility input filter installed."); 166 } 167 mInstalled = true; 168 disableFeatures(); 169 enableFeatures(); 170 super.onInstalled(); 171 } 172 173 @Override 174 public void onUninstalled() { 175 if (DEBUG) { 176 Slog.d(TAG, "Accessibility input filter uninstalled."); 177 } 178 mInstalled = false; 179 disableFeatures(); 180 super.onUninstalled(); 181 } 182 183 @Override 184 public void onInputEvent(InputEvent event, int policyFlags) { 185 if (DEBUG) { 186 Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" 187 + Integer.toHexString(policyFlags)); 188 } 189 190 if (mEventHandler == null) { 191 super.onInputEvent(event, policyFlags); 192 return; 193 } 194 195 EventStreamState state = getEventStreamState(event); 196 if (state == null) { 197 super.onInputEvent(event, policyFlags); 198 return; 199 } 200 201 int eventSource = event.getSource(); 202 if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { 203 state.reset(); 204 mEventHandler.clearEvents(eventSource); 205 super.onInputEvent(event, policyFlags); 206 return; 207 } 208 209 if (state.updateDeviceId(event.getDeviceId())) { 210 mEventHandler.clearEvents(eventSource); 211 } 212 213 if (!state.deviceIdValid()) { 214 super.onInputEvent(event, policyFlags); 215 return; 216 } 217 218 if (event instanceof MotionEvent) { 219 if ((mEnabledFeatures & FEATURES_AFFECTING_MOTION_EVENTS) != 0) { 220 MotionEvent motionEvent = (MotionEvent) event; 221 processMotionEvent(state, motionEvent, policyFlags); 222 return; 223 } else { 224 super.onInputEvent(event, policyFlags); 225 } 226 } else if (event instanceof KeyEvent) { 227 KeyEvent keyEvent = (KeyEvent) event; 228 processKeyEvent(state, keyEvent, policyFlags); 229 } 230 } 231 232 /** 233 * Gets current event stream state associated with an input event. 234 * @return The event stream state that should be used for the event. Null if the event should 235 * not be handled by #AccessibilityInputFilter. 236 */ 237 private EventStreamState getEventStreamState(InputEvent event) { 238 if (event instanceof MotionEvent) { 239 if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { 240 if (mTouchScreenStreamState == null) { 241 mTouchScreenStreamState = new TouchScreenEventStreamState(); 242 } 243 return mTouchScreenStreamState; 244 } 245 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { 246 if (mMouseStreamState == null) { 247 mMouseStreamState = new MouseEventStreamState(); 248 } 249 return mMouseStreamState; 250 } 251 } else if (event instanceof KeyEvent) { 252 if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { 253 if (mKeyboardStreamState == null) { 254 mKeyboardStreamState = new KeyboardEventStreamState(); 255 } 256 return mKeyboardStreamState; 257 } 258 } 259 return null; 260 } 261 262 private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) { 263 if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) { 264 super.onInputEvent(event, policyFlags); 265 return; 266 } 267 268 if (!state.shouldProcessMotionEvent(event)) { 269 return; 270 } 271 272 batchMotionEvent(event, policyFlags); 273 } 274 275 private void processKeyEvent(EventStreamState state, KeyEvent event, int policyFlags) { 276 if (!state.shouldProcessKeyEvent(event)) { 277 super.onInputEvent(event, policyFlags); 278 return; 279 } 280 mEventHandler.onKeyEvent(event, policyFlags); 281 } 282 283 private void scheduleProcessBatchedEvents() { 284 mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, 285 mProcessBatchedEventsRunnable, null); 286 } 287 288 private void batchMotionEvent(MotionEvent event, int policyFlags) { 289 if (DEBUG) { 290 Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags); 291 } 292 if (mEventQueue == null) { 293 mEventQueue = MotionEventHolder.obtain(event, policyFlags); 294 scheduleProcessBatchedEvents(); 295 return; 296 } 297 if (mEventQueue.event.addBatch(event)) { 298 return; 299 } 300 MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags); 301 holder.next = mEventQueue; 302 mEventQueue.previous = holder; 303 mEventQueue = holder; 304 } 305 306 private void processBatchedEvents(long frameNanos) { 307 MotionEventHolder current = mEventQueue; 308 if (current == null) { 309 return; 310 } 311 while (current.next != null) { 312 current = current.next; 313 } 314 while (true) { 315 if (current == null) { 316 mEventQueue = null; 317 break; 318 } 319 if (current.event.getEventTimeNano() >= frameNanos) { 320 // Finished with this choreographer frame. Do the rest on the next one. 321 current.next = null; 322 break; 323 } 324 handleMotionEvent(current.event, current.policyFlags); 325 MotionEventHolder prior = current; 326 current = current.previous; 327 prior.recycle(); 328 } 329 } 330 331 private void handleMotionEvent(MotionEvent event, int policyFlags) { 332 if (DEBUG) { 333 Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags); 334 } 335 // Since we do batch processing it is possible that by the time the 336 // next batch is processed the event handle had been set to null. 337 if (mEventHandler != null) { 338 mPm.userActivity(event.getEventTime(), false); 339 MotionEvent transformedEvent = MotionEvent.obtain(event); 340 mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); 341 transformedEvent.recycle(); 342 } 343 } 344 345 @Override 346 public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, 347 int policyFlags) { 348 sendInputEvent(transformedEvent, policyFlags); 349 } 350 351 @Override 352 public void onKeyEvent(KeyEvent event, int policyFlags) { 353 sendInputEvent(event, policyFlags); 354 } 355 356 @Override 357 public void onAccessibilityEvent(AccessibilityEvent event) { 358 // TODO Implement this to inject the accessibility event 359 // into the accessibility manager service similarly 360 // to how this is done for input events. 361 } 362 363 @Override 364 public void setNext(EventStreamTransformation sink) { 365 /* do nothing */ 366 } 367 368 @Override 369 public void clearEvents(int inputSource) { 370 /* do nothing */ 371 } 372 373 void setUserAndEnabledFeatures(int userId, int enabledFeatures) { 374 if (mEnabledFeatures == enabledFeatures && mUserId == userId) { 375 return; 376 } 377 if (mInstalled) { 378 disableFeatures(); 379 } 380 mUserId = userId; 381 mEnabledFeatures = enabledFeatures; 382 if (mInstalled) { 383 enableFeatures(); 384 } 385 } 386 387 void notifyAccessibilityEvent(AccessibilityEvent event) { 388 if (mEventHandler != null) { 389 mEventHandler.onAccessibilityEvent(event); 390 } 391 } 392 393 void notifyAccessibilityButtonClicked() { 394 if (mMagnificationGestureHandler != null) { 395 mMagnificationGestureHandler.notifyShortcutTriggered(); 396 } 397 } 398 399 private void enableFeatures() { 400 resetStreamState(); 401 402 if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { 403 mAutoclickController = new AutoclickController(mContext, mUserId); 404 addFirstEventHandler(mAutoclickController); 405 } 406 407 if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { 408 mTouchExplorer = new TouchExplorer(mContext, mAms); 409 addFirstEventHandler(mTouchExplorer); 410 } 411 412 if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 413 || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) 414 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { 415 final boolean detectControlGestures = (mEnabledFeatures 416 & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; 417 final boolean triggerable = (mEnabledFeatures 418 & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; 419 mMagnificationGestureHandler = new MagnificationGestureHandler( 420 mContext, mAms, detectControlGestures, triggerable); 421 addFirstEventHandler(mMagnificationGestureHandler); 422 } 423 424 if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { 425 mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper()); 426 addFirstEventHandler(mMotionEventInjector); 427 mAms.setMotionEventInjector(mMotionEventInjector); 428 } 429 430 if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { 431 mKeyboardInterceptor = new KeyboardInterceptor(mAms, 432 LocalServices.getService(WindowManagerPolicy.class)); 433 addFirstEventHandler(mKeyboardInterceptor); 434 } 435 } 436 437 /** 438 * Adds an event handler to the event handler chain. The handler is added at the beginning of 439 * the chain. 440 * 441 * @param handler The handler to be added to the event handlers list. 442 */ 443 private void addFirstEventHandler(EventStreamTransformation handler) { 444 if (mEventHandler != null) { 445 handler.setNext(mEventHandler); 446 } else { 447 handler.setNext(this); 448 } 449 mEventHandler = handler; 450 } 451 452 private void disableFeatures() { 453 // Give the features a chance to process any batched events so we'll keep a consistent 454 // event stream 455 processBatchedEvents(Long.MAX_VALUE); 456 if (mMotionEventInjector != null) { 457 mAms.setMotionEventInjector(null); 458 mMotionEventInjector.onDestroy(); 459 mMotionEventInjector = null; 460 } 461 if (mAutoclickController != null) { 462 mAutoclickController.onDestroy(); 463 mAutoclickController = null; 464 } 465 if (mTouchExplorer != null) { 466 mTouchExplorer.onDestroy(); 467 mTouchExplorer = null; 468 } 469 if (mMagnificationGestureHandler != null) { 470 mMagnificationGestureHandler.onDestroy(); 471 mMagnificationGestureHandler = null; 472 } 473 if (mKeyboardInterceptor != null) { 474 mKeyboardInterceptor.onDestroy(); 475 mKeyboardInterceptor = null; 476 } 477 478 mEventHandler = null; 479 resetStreamState(); 480 } 481 482 void resetStreamState() { 483 if (mTouchScreenStreamState != null) { 484 mTouchScreenStreamState.reset(); 485 } 486 if (mMouseStreamState != null) { 487 mMouseStreamState.reset(); 488 } 489 if (mKeyboardStreamState != null) { 490 mKeyboardStreamState.reset(); 491 } 492 } 493 494 @Override 495 public void onDestroy() { 496 /* ignore */ 497 } 498 499 private static class MotionEventHolder { 500 private static final int MAX_POOL_SIZE = 32; 501 private static final SimplePool<MotionEventHolder> sPool = 502 new SimplePool<MotionEventHolder>(MAX_POOL_SIZE); 503 504 public int policyFlags; 505 public MotionEvent event; 506 public MotionEventHolder next; 507 public MotionEventHolder previous; 508 509 public static MotionEventHolder obtain(MotionEvent event, int policyFlags) { 510 MotionEventHolder holder = sPool.acquire(); 511 if (holder == null) { 512 holder = new MotionEventHolder(); 513 } 514 holder.event = MotionEvent.obtain(event); 515 holder.policyFlags = policyFlags; 516 return holder; 517 } 518 519 public void recycle() { 520 event.recycle(); 521 event = null; 522 policyFlags = 0; 523 next = null; 524 previous = null; 525 sPool.release(this); 526 } 527 } 528 529 /** 530 * Keeps state of event streams observed for an input device with a certain source. 531 * Provides information about whether motion and key events should be processed by accessibility 532 * #EventStreamTransformations. Base implementation describes behaviour for event sources that 533 * whose events should not be handled by a11y event stream transformations. 534 */ 535 private static class EventStreamState { 536 private int mDeviceId; 537 538 EventStreamState() { 539 mDeviceId = -1; 540 } 541 542 /** 543 * Updates the ID of the device associated with the state. If the ID changes, resets 544 * internal state. 545 * 546 * @param deviceId Updated input device ID. 547 * @return Whether the device ID has changed. 548 */ 549 public boolean updateDeviceId(int deviceId) { 550 if (mDeviceId == deviceId) { 551 return false; 552 } 553 // Reset clears internal state, so make sure it's called before |mDeviceId| is updated. 554 reset(); 555 mDeviceId = deviceId; 556 return true; 557 } 558 559 /** 560 * @return Whether device ID is valid. 561 */ 562 public boolean deviceIdValid() { 563 return mDeviceId >= 0; 564 } 565 566 /** 567 * Resets the event stream state. 568 */ 569 public void reset() { 570 mDeviceId = -1; 571 } 572 573 /** 574 * @return Whether scroll events for device should be handled by event transformations. 575 */ 576 public boolean shouldProcessScroll() { 577 return false; 578 } 579 580 /** 581 * @param event An observed motion event. 582 * @return Whether the event should be handled by event transformations. 583 */ 584 public boolean shouldProcessMotionEvent(MotionEvent event) { 585 return false; 586 } 587 588 /** 589 * @param event An observed key event. 590 * @return Whether the event should be handled by event transformations. 591 */ 592 public boolean shouldProcessKeyEvent(KeyEvent event) { 593 return false; 594 } 595 } 596 597 /** 598 * Keeps state of stream of events from a mouse device. 599 */ 600 private static class MouseEventStreamState extends EventStreamState { 601 private boolean mMotionSequenceStarted; 602 603 public MouseEventStreamState() { 604 reset(); 605 } 606 607 @Override 608 final public void reset() { 609 super.reset(); 610 mMotionSequenceStarted = false; 611 } 612 613 @Override 614 final public boolean shouldProcessScroll() { 615 return true; 616 } 617 618 @Override 619 final public boolean shouldProcessMotionEvent(MotionEvent event) { 620 if (mMotionSequenceStarted) { 621 return true; 622 } 623 // Wait for down or move event to start processing mouse events. 624 int action = event.getActionMasked(); 625 mMotionSequenceStarted = 626 action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_HOVER_MOVE; 627 return mMotionSequenceStarted; 628 } 629 } 630 631 /** 632 * Keeps state of stream of events from a touch screen device. 633 */ 634 private static class TouchScreenEventStreamState extends EventStreamState { 635 private boolean mTouchSequenceStarted; 636 private boolean mHoverSequenceStarted; 637 638 public TouchScreenEventStreamState() { 639 reset(); 640 } 641 642 @Override 643 final public void reset() { 644 super.reset(); 645 mTouchSequenceStarted = false; 646 mHoverSequenceStarted = false; 647 } 648 649 @Override 650 final public boolean shouldProcessMotionEvent(MotionEvent event) { 651 // Wait for a down touch event to start processing. 652 if (event.isTouchEvent()) { 653 if (mTouchSequenceStarted) { 654 return true; 655 } 656 mTouchSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_DOWN; 657 return mTouchSequenceStarted; 658 } 659 660 // Wait for an enter hover event to start processing. 661 if (mHoverSequenceStarted) { 662 return true; 663 } 664 mHoverSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER; 665 return mHoverSequenceStarted; 666 } 667 } 668 669 /** 670 * Keeps state of streams of events from all keyboard devices. 671 */ 672 private static class KeyboardEventStreamState extends EventStreamState { 673 private SparseBooleanArray mEventSequenceStartedMap = new SparseBooleanArray(); 674 675 public KeyboardEventStreamState() { 676 reset(); 677 } 678 679 @Override 680 final public void reset() { 681 super.reset(); 682 mEventSequenceStartedMap.clear(); 683 } 684 685 /* 686 * Key events from different devices may be interleaved. For example, the volume up and 687 * down keys can come from different device IDs. 688 */ 689 @Override 690 public boolean updateDeviceId(int deviceId) { 691 return false; 692 } 693 694 // We manage all device ids simultaneously; there is no concept of validity. 695 @Override 696 public boolean deviceIdValid() { 697 return true; 698 } 699 700 701 @Override 702 final public boolean shouldProcessKeyEvent(KeyEvent event) { 703 // For each keyboard device, wait for a down event from a device to start processing 704 int deviceId = event.getDeviceId(); 705 if (mEventSequenceStartedMap.get(deviceId, false)) { 706 return true; 707 } 708 boolean shouldProcess = event.getAction() == KeyEvent.ACTION_DOWN; 709 mEventSequenceStartedMap.put(deviceId, shouldProcess); 710 return shouldProcess; 711 } 712 } 713 } 714