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