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