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