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                 super.onInputEvent(event, policyFlags);
    223                 return;
    224             }
    225             mKeyEventSequenceStarted = true;
    226         }
    227         mAms.notifyKeyEvent(event, policyFlags);
    228     }
    229 
    230     private void scheduleProcessBatchedEvents() {
    231         mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
    232                 mProcessBatchedEventsRunnable, null);
    233     }
    234 
    235     private void batchMotionEvent(MotionEvent event, int policyFlags) {
    236         if (DEBUG) {
    237             Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
    238         }
    239         if (mEventQueue == null) {
    240             mEventQueue = MotionEventHolder.obtain(event, policyFlags);
    241             scheduleProcessBatchedEvents();
    242             return;
    243         }
    244         if (mEventQueue.event.addBatch(event)) {
    245             return;
    246         }
    247         MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
    248         holder.next = mEventQueue;
    249         mEventQueue.previous = holder;
    250         mEventQueue = holder;
    251     }
    252 
    253     private void processBatchedEvents(long frameNanos) {
    254         MotionEventHolder current = mEventQueue;
    255         while (current.next != null) {
    256             current = current.next;
    257         }
    258         while (true) {
    259             if (current == null) {
    260                 mEventQueue = null;
    261                 break;
    262             }
    263             if (current.event.getEventTimeNano() >= frameNanos) {
    264                 // Finished with this choreographer frame. Do the rest on the next one.
    265                 current.next = null;
    266                 break;
    267             }
    268             handleMotionEvent(current.event, current.policyFlags);
    269             MotionEventHolder prior = current;
    270             current = current.previous;
    271             prior.recycle();
    272         }
    273     }
    274 
    275     private void handleMotionEvent(MotionEvent event, int policyFlags) {
    276         if (DEBUG) {
    277             Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
    278         }
    279         // Since we do batch processing it is possible that by the time the
    280         // next batch is processed the event handle had been set to null.
    281         if (mEventHandler != null) {
    282             mPm.userActivity(event.getEventTime(), false);
    283             MotionEvent transformedEvent = MotionEvent.obtain(event);
    284             mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
    285             transformedEvent.recycle();
    286         }
    287     }
    288 
    289     @Override
    290     public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
    291             int policyFlags) {
    292         sendInputEvent(transformedEvent, policyFlags);
    293     }
    294 
    295     @Override
    296     public void onAccessibilityEvent(AccessibilityEvent event) {
    297         // TODO Implement this to inject the accessibility event
    298         //      into the accessibility manager service similarly
    299         //      to how this is done for input events.
    300     }
    301 
    302     @Override
    303     public void setNext(EventStreamTransformation sink) {
    304         /* do nothing */
    305     }
    306 
    307     @Override
    308     public void clear() {
    309         /* do nothing */
    310     }
    311 
    312     void setEnabledFeatures(int enabledFeatures) {
    313         if (mEnabledFeatures == enabledFeatures) {
    314             return;
    315         }
    316         if (mInstalled) {
    317             disableFeatures();
    318         }
    319         mEnabledFeatures = enabledFeatures;
    320         if (mInstalled) {
    321             enableFeatures();
    322         }
    323     }
    324 
    325     void notifyAccessibilityEvent(AccessibilityEvent event) {
    326         if (mEventHandler != null) {
    327             mEventHandler.onAccessibilityEvent(event);
    328         }
    329     }
    330 
    331     private void enableFeatures() {
    332         mMotionEventSequenceStarted = false;
    333         mHoverEventSequenceStarted = false;
    334         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
    335             mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext,
    336                     Display.DEFAULT_DISPLAY, mAms);
    337             mEventHandler.setNext(this);
    338         }
    339         if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
    340             mTouchExplorer = new TouchExplorer(mContext, mAms);
    341             mTouchExplorer.setNext(this);
    342             if (mEventHandler != null) {
    343                 mEventHandler.setNext(mTouchExplorer);
    344             } else {
    345                 mEventHandler = mTouchExplorer;
    346             }
    347         }
    348         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
    349             mFilterKeyEvents = true;
    350         }
    351     }
    352 
    353     void disableFeatures() {
    354         if (mTouchExplorer != null) {
    355             mTouchExplorer.clear();
    356             mTouchExplorer.onDestroy();
    357             mTouchExplorer = null;
    358         }
    359         if (mScreenMagnifier != null) {
    360             mScreenMagnifier.clear();
    361             mScreenMagnifier.onDestroy();
    362             mScreenMagnifier = null;
    363         }
    364         mEventHandler = null;
    365         mKeyEventSequenceStarted = false;
    366         mMotionEventSequenceStarted = false;
    367         mHoverEventSequenceStarted = false;
    368         mFilterKeyEvents = false;
    369     }
    370 
    371     @Override
    372     public void onDestroy() {
    373         /* ignore */
    374     }
    375 
    376     private static class MotionEventHolder {
    377         private static final int MAX_POOL_SIZE = 32;
    378         private static final SimplePool<MotionEventHolder> sPool =
    379                 new SimplePool<MotionEventHolder>(MAX_POOL_SIZE);
    380 
    381         public int policyFlags;
    382         public MotionEvent event;
    383         public MotionEventHolder next;
    384         public MotionEventHolder previous;
    385 
    386         public static MotionEventHolder obtain(MotionEvent event, int policyFlags) {
    387             MotionEventHolder holder = sPool.acquire();
    388             if (holder == null) {
    389                 holder = new MotionEventHolder();
    390             }
    391             holder.event = MotionEvent.obtain(event);
    392             holder.policyFlags = policyFlags;
    393             return holder;
    394         }
    395 
    396         public void recycle() {
    397             event.recycle();
    398             event = null;
    399             policyFlags = 0;
    400             next = null;
    401             previous = null;
    402             sPool.release(this);
    403         }
    404     }
    405 }
    406