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