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