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