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