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.view.Choreographer;
     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.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 #setEnabledFeatures(int)
     49      */
     50     static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
     51 
     52     /**
     53      * Flag for enabling the touch exploration feature.
     54      *
     55      * @see #setEnabledFeatures(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 #setEnabledFeatures(int)
     63      */
     64     static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004;
     65 
     66     private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
     67         @Override
     68         public void run() {
     69             final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
     70             if (DEBUG) {
     71                 Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos);
     72             }
     73             processBatchedEvents(frameTimeNanos);
     74             if (DEBUG) {
     75                 Slog.i(TAG, "End batch processing.");
     76             }
     77             if (mEventQueue != null) {
     78                 scheduleProcessBatchedEvents();
     79             }
     80         }
     81     };
     82 
     83     private final Context mContext;
     84 
     85     private final PowerManager mPm;
     86 
     87     private final AccessibilityManagerService mAms;
     88 
     89     private final Choreographer mChoreographer;
     90 
     91     private int mCurrentTouchDeviceId;
     92 
     93     private boolean mInstalled;
     94 
     95     private int mEnabledFeatures;
     96 
     97     private TouchExplorer mTouchExplorer;
     98 
     99     private ScreenMagnifier mScreenMagnifier;
    100 
    101     private EventStreamTransformation mEventHandler;
    102 
    103     private MotionEventHolder mEventQueue;
    104 
    105     private boolean mMotionEventSequenceStarted;
    106 
    107     private boolean mHoverEventSequenceStarted;
    108 
    109     private boolean mKeyEventSequenceStarted;
    110 
    111     private boolean mFilterKeyEvents;
    112 
    113     AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
    114         super(context.getMainLooper());
    115         mContext = context;
    116         mAms = service;
    117         mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    118         mChoreographer = Choreographer.getInstance();
    119     }
    120 
    121     @Override
    122     public void onInstalled() {
    123         if (DEBUG) {
    124             Slog.d(TAG, "Accessibility input filter installed.");
    125         }
    126         mInstalled = true;
    127         disableFeatures();
    128         enableFeatures();
    129         super.onInstalled();
    130     }
    131 
    132     @Override
    133     public void onUninstalled() {
    134         if (DEBUG) {
    135             Slog.d(TAG, "Accessibility input filter uninstalled.");
    136         }
    137         mInstalled = false;
    138         disableFeatures();
    139         super.onUninstalled();
    140     }
    141 
    142     @Override
    143     public void onInputEvent(InputEvent event, int policyFlags) {
    144         if (DEBUG) {
    145             Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
    146                     + Integer.toHexString(policyFlags));
    147         }
    148         if (event instanceof MotionEvent
    149                 && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
    150             MotionEvent motionEvent = (MotionEvent) event;
    151             onMotionEvent(motionEvent, policyFlags);
    152         } else if (event instanceof KeyEvent
    153                 && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
    154             KeyEvent keyEvent = (KeyEvent) event;
    155             onKeyEvent(keyEvent, policyFlags);
    156         } else {
    157             super.onInputEvent(event, policyFlags);
    158         }
    159     }
    160 
    161     private void onMotionEvent(MotionEvent event, int policyFlags) {
    162         if (mEventHandler == null) {
    163             super.onInputEvent(event, policyFlags);
    164             return;
    165         }
    166         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
    167             mMotionEventSequenceStarted = false;
    168             mHoverEventSequenceStarted = false;
    169             mEventHandler.clear();
    170             super.onInputEvent(event, policyFlags);
    171             return;
    172         }
    173         final int deviceId = event.getDeviceId();
    174         if (mCurrentTouchDeviceId != deviceId) {
    175             mCurrentTouchDeviceId = deviceId;
    176             mMotionEventSequenceStarted = false;
    177             mHoverEventSequenceStarted = false;
    178             mEventHandler.clear();
    179         }
    180         if (mCurrentTouchDeviceId < 0) {
    181             super.onInputEvent(event, policyFlags);
    182             return;
    183         }
    184         // We do not handle scroll events.
    185         if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
    186             super.onInputEvent(event, policyFlags);
    187             return;
    188         }
    189         // Wait for a down touch event to start processing.
    190         if (event.isTouchEvent()) {
    191             if (!mMotionEventSequenceStarted) {
    192                 if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
    193                     return;
    194                 }
    195                 mMotionEventSequenceStarted = true;
    196             }
    197         } else {
    198             // Wait for an enter hover event to start processing.
    199             if (!mHoverEventSequenceStarted) {
    200                 if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
    201                     return;
    202                 }
    203                 mHoverEventSequenceStarted = true;
    204             }
    205         }
    206         batchMotionEvent((MotionEvent) event, policyFlags);
    207     }
    208 
    209     private void onKeyEvent(KeyEvent event, int policyFlags) {
    210         if (!mFilterKeyEvents) {
    211             super.onInputEvent(event, policyFlags);
    212             return;
    213         }
    214         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
    215             mKeyEventSequenceStarted = false;
    216             super.onInputEvent(event, policyFlags);
    217             return;
    218         }
    219         // Wait for a down key event to start processing.
    220         if (!mKeyEventSequenceStarted) {
    221             if (event.getAction() != KeyEvent.ACTION_DOWN) {
    222                 return;
    223             }
    224             mKeyEventSequenceStarted = true;
    225         }
    226         mAms.notifyKeyEvent(event, policyFlags);
    227     }
    228 
    229     private void scheduleProcessBatchedEvents() {
    230         mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
    231                 mProcessBatchedEventsRunnable, null);
    232     }
    233 
    234     private void batchMotionEvent(MotionEvent event, int policyFlags) {
    235         if (DEBUG) {
    236             Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
    237         }
    238         if (mEventQueue == null) {
    239             mEventQueue = MotionEventHolder.obtain(event, policyFlags);
    240             scheduleProcessBatchedEvents();
    241             return;
    242         }
    243         if (mEventQueue.event.addBatch(event)) {
    244             return;
    245         }
    246         MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
    247         holder.next = mEventQueue;
    248         mEventQueue.previous = holder;
    249         mEventQueue = holder;
    250     }
    251 
    252     private void processBatchedEvents(long frameNanos) {
    253         MotionEventHolder current = mEventQueue;
    254         while (current.next != null) {
    255             current = current.next;
    256         }
    257         while (true) {
    258             if (current == null) {
    259                 mEventQueue = null;
    260                 break;
    261             }
    262             if (current.event.getEventTimeNano() >= frameNanos) {
    263                 // Finished with this choreographer frame. Do the rest on the next one.
    264                 current.next = null;
    265                 break;
    266             }
    267             handleMotionEvent(current.event, current.policyFlags);
    268             MotionEventHolder prior = current;
    269             current = current.previous;
    270             prior.recycle();
    271         }
    272     }
    273 
    274     private void handleMotionEvent(MotionEvent event, int policyFlags) {
    275         if (DEBUG) {
    276             Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
    277         }
    278         // Since we do batch processing it is possible that by the time the
    279         // next batch is processed the event handle had been set to null.
    280         if (mEventHandler != null) {
    281             mPm.userActivity(event.getEventTime(), false);
    282             MotionEvent transformedEvent = MotionEvent.obtain(event);
    283             mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
    284             transformedEvent.recycle();
    285         }
    286     }
    287 
    288     @Override
    289     public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
    290             int policyFlags) {
    291         sendInputEvent(transformedEvent, policyFlags);
    292     }
    293 
    294     @Override
    295     public void onAccessibilityEvent(AccessibilityEvent event) {
    296         // TODO Implement this to inject the accessibility event
    297         //      into the accessibility manager service similarly
    298         //      to how this is done for input events.
    299     }
    300 
    301     @Override
    302     public void setNext(EventStreamTransformation sink) {
    303         /* do nothing */
    304     }
    305 
    306     @Override
    307     public void clear() {
    308         /* do nothing */
    309     }
    310 
    311     void setEnabledFeatures(int enabledFeatures) {
    312         if (mEnabledFeatures == enabledFeatures) {
    313             return;
    314         }
    315         if (mInstalled) {
    316             disableFeatures();
    317         }
    318         mEnabledFeatures = enabledFeatures;
    319         if (mInstalled) {
    320             enableFeatures();
    321         }
    322     }
    323 
    324     void notifyAccessibilityEvent(AccessibilityEvent event) {
    325         if (mEventHandler != null) {
    326             mEventHandler.onAccessibilityEvent(event);
    327         }
    328     }
    329 
    330     private void enableFeatures() {
    331         mMotionEventSequenceStarted = false;
    332         mHoverEventSequenceStarted = false;
    333         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
    334             mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext,
    335                     Display.DEFAULT_DISPLAY, mAms);
    336             mEventHandler.setNext(this);
    337         }
    338         if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
    339             mTouchExplorer = new TouchExplorer(mContext, mAms);
    340             mTouchExplorer.setNext(this);
    341             if (mEventHandler != null) {
    342                 mEventHandler.setNext(mTouchExplorer);
    343             } else {
    344                 mEventHandler = mTouchExplorer;
    345             }
    346         }
    347         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
    348             mFilterKeyEvents = true;
    349         }
    350     }
    351 
    352     void disableFeatures() {
    353         if (mTouchExplorer != null) {
    354             mTouchExplorer.clear();
    355             mTouchExplorer.onDestroy();
    356             mTouchExplorer = null;
    357         }
    358         if (mScreenMagnifier != null) {
    359             mScreenMagnifier.clear();
    360             mScreenMagnifier.onDestroy();
    361             mScreenMagnifier = null;
    362         }
    363         mEventHandler = null;
    364         mKeyEventSequenceStarted = false;
    365         mMotionEventSequenceStarted = false;
    366         mHoverEventSequenceStarted = false;
    367         mFilterKeyEvents = false;
    368     }
    369 
    370     @Override
    371     public void onDestroy() {
    372         /* ignore */
    373     }
    374 
    375     private static class MotionEventHolder {
    376         private static final int MAX_POOL_SIZE = 32;
    377         private static final SimplePool<MotionEventHolder> sPool =
    378                 new SimplePool<MotionEventHolder>(MAX_POOL_SIZE);
    379 
    380         public int policyFlags;
    381         public MotionEvent event;
    382         public MotionEventHolder next;
    383         public MotionEventHolder previous;
    384 
    385         public static MotionEventHolder obtain(MotionEvent event, int policyFlags) {
    386             MotionEventHolder holder = sPool.acquire();
    387             if (holder == null) {
    388                 holder = new MotionEventHolder();
    389             }
    390             holder.event = MotionEvent.obtain(event);
    391             holder.policyFlags = policyFlags;
    392             return holder;
    393         }
    394 
    395         public void recycle() {
    396             event.recycle();
    397             event = null;
    398             policyFlags = 0;
    399             next = null;
    400             previous = null;
    401             sPool.release(this);
    402         }
    403     }
    404 }
    405