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