Home | History | Annotate | Download | only in accessibility
      1 /*
      2  ** Copyright 2015, 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.os.Binder;
     20 import android.os.Handler;
     21 import android.os.Message;
     22 import android.os.PowerManager;
     23 import android.util.ArrayMap;
     24 import android.util.Pools;
     25 import android.util.Pools.Pool;
     26 import android.util.Slog;
     27 import android.view.InputEventConsistencyVerifier;
     28 import android.view.KeyEvent;
     29 import android.view.WindowManagerPolicy;
     30 
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 import java.util.Map;
     34 
     35 /**
     36  * Dispatcher to send KeyEvents to all accessibility services that are able to process them.
     37  * Events that are handled by one or more services are consumed. Events that are not processed
     38  * by any service (or time out before a service reports them as handled) are passed along to
     39  * the rest of the system.
     40  *
     41  * The class assumes that services report their return values in order, which is valid because
     42  * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so
     43  * don't see the N+1th event until they have processed the Nth event.
     44  */
     45 public class KeyEventDispatcher implements Handler.Callback {
     46     // Debugging
     47     private static final String LOG_TAG = "KeyEventDispatcher";
     48     private static final boolean DEBUG = false;
     49     /* KeyEvents must be processed in this time interval */
     50     private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
     51     public static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
     52     private static final int MAX_POOL_SIZE = 10;
     53 
     54     private final Pool<PendingKeyEvent> mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE);
     55     private final Object mLock;
     56 
     57     /*
     58      * Track events sent to each filter. If a KeyEvent is to be sent to at least one service,
     59      * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in
     60      * the list for each service its KeyEvent is sent to. It is removed from the list when
     61      * the service calls setOnKeyEventResult, or when we time out waiting for the service to
     62      * respond.
     63      */
     64     private final Map<KeyEventFilter, ArrayList<PendingKeyEvent>> mPendingEventsMap =
     65             new ArrayMap<>();
     66 
     67     private final InputEventConsistencyVerifier mSentEventsVerifier;
     68     private final Handler mHandlerToSendKeyEventsToInputFilter;
     69     private final int mMessageTypeForSendKeyEvent;
     70     private final PowerManager mPowerManager;
     71     private Handler mKeyEventTimeoutHandler;
     72 
     73     /**
     74      * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s
     75      * that have not been handled by any accessibility service.
     76      * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the
     77      * message that carries a {@code KeyEvent} to be sent to the input filter
     78      * @param lock The lock used for all synchronization in this package. This lock must be held
     79      * when calling {@code notifyKeyEventLocked}
     80      * @param powerManager The power manager to alert to user activity if a KeyEvent is processed
     81      * by a service
     82      */
     83     public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
     84             int messageTypeForSendKeyEvent, Object lock, PowerManager powerManager) {
     85         if (InputEventConsistencyVerifier.isInstrumentationEnabled()) {
     86             mSentEventsVerifier = new InputEventConsistencyVerifier(
     87                     this, 0, KeyEventDispatcher.class.getSimpleName());
     88         } else {
     89             mSentEventsVerifier = null;
     90         }
     91         mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter;
     92         mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent;
     93         mKeyEventTimeoutHandler =
     94                 new Handler(handlerToSendKeyEventsToInputFilter.getLooper(), this);
     95         mLock = lock;
     96         mPowerManager = powerManager;
     97     }
     98 
     99     /**
    100      * See above for most params
    101      * @param timeoutHandler Specify a handler to use for handling timeouts. This internal state is
    102      * exposed for testing.
    103      */
    104     public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
    105             int messageTypeForSendKeyEvent, Object lock, PowerManager powerManager,
    106             Handler timeoutHandler) {
    107         this(handlerToSendKeyEventsToInputFilter, messageTypeForSendKeyEvent, lock, powerManager);
    108         mKeyEventTimeoutHandler = timeoutHandler;
    109     }
    110 
    111     /**
    112      * Notify that a new KeyEvent is available to accessibility services. Must be called with the
    113      * lock used to construct this object held. The keyEventFilters list must also be protected
    114      * by the lock.
    115      *
    116      * @param event The new key event
    117      * @param policyFlags Flags for the event
    118      * @param keyEventFilters A list of keyEventFilters that should be considered for processing
    119      * this event
    120      *
    121      * @return {@code true} if the event was passed to at least one AccessibilityService,
    122      * {@code false} otherwise.
    123      */
    124     // TODO: The locking policy for keyEventFilters needs some thought.
    125     public boolean notifyKeyEventLocked(
    126             KeyEvent event, int policyFlags, List<? extends KeyEventFilter> keyEventFilters) {
    127         PendingKeyEvent pendingKeyEvent = null;
    128         KeyEvent localClone = KeyEvent.obtain(event);
    129         for (int i = 0; i < keyEventFilters.size(); i++) {
    130             KeyEventFilter keyEventFilter = keyEventFilters.get(i);
    131             if (keyEventFilter.onKeyEvent(localClone, localClone.getSequenceNumber())) {
    132                 if (pendingKeyEvent == null) {
    133                     pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
    134                 }
    135                 ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(keyEventFilter);
    136                 if (pendingEventList == null) {
    137                     pendingEventList = new ArrayList<>();
    138                     mPendingEventsMap.put(keyEventFilter, pendingEventList);
    139                 }
    140                 pendingEventList.add(pendingKeyEvent);
    141                 pendingKeyEvent.referenceCount++;
    142             }
    143         }
    144 
    145         if (pendingKeyEvent == null) {
    146             localClone.recycle();
    147             return false;
    148         }
    149 
    150         Message message = mKeyEventTimeoutHandler.obtainMessage(
    151                 MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
    152         mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
    153         return true;
    154     }
    155 
    156     /**
    157      * Set the result from onKeyEvent from one service.
    158      *
    159      * @param keyEventFilter The filter setting the result
    160      * @param handled {@code true} if the service handled the {@code KeyEvent}
    161      * @param sequence The sequence number of the {@code KeyEvent}
    162      */
    163     public void setOnKeyEventResult(KeyEventFilter keyEventFilter, boolean handled, int sequence) {
    164         synchronized (mLock) {
    165             PendingKeyEvent pendingEvent =
    166                     removeEventFromListLocked(mPendingEventsMap.get(keyEventFilter), sequence);
    167             if (pendingEvent != null) {
    168                 if (handled && !pendingEvent.handled) {
    169                     pendingEvent.handled = handled;
    170                     final long identity = Binder.clearCallingIdentity();
    171                     try {
    172                         mPowerManager.userActivity(pendingEvent.event.getEventTime(),
    173                                 PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
    174                     } finally {
    175                         Binder.restoreCallingIdentity(identity);
    176                     }
    177                 }
    178                 removeReferenceToPendingEventLocked(pendingEvent);
    179             }
    180         }
    181     }
    182 
    183     /**
    184      * Flush all pending key events for a service, treating all of them as unhandled
    185      *
    186      * @param keyEventFilter The filter for which to flush events
    187      */
    188     public void flush(KeyEventFilter keyEventFilter) {
    189         synchronized (mLock) {
    190             List<PendingKeyEvent> pendingEvents = mPendingEventsMap.get(keyEventFilter);
    191             if (pendingEvents != null) {
    192                 for (int i = 0; i < pendingEvents.size(); i++) {
    193                     PendingKeyEvent pendingEvent = pendingEvents.get(i);
    194                     removeReferenceToPendingEventLocked(pendingEvent);
    195                 }
    196                 mPendingEventsMap.remove(keyEventFilter);
    197             }
    198         }
    199     }
    200 
    201     @Override
    202     public boolean handleMessage(Message message) {
    203         if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
    204             Slog.w(LOG_TAG, "Unknown message: " + message.what);
    205             return false;
    206         }
    207         PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
    208         synchronized (mLock) {
    209             for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
    210                 if (listForService.remove(pendingKeyEvent)) {
    211                     if(removeReferenceToPendingEventLocked(pendingKeyEvent)) {
    212                         break;
    213                     }
    214                 }
    215             }
    216         }
    217         return true;
    218     }
    219 
    220     private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) {
    221         PendingKeyEvent pendingEvent = mPendingEventPool.acquire();
    222         if (pendingEvent == null) {
    223             pendingEvent = new PendingKeyEvent();
    224         }
    225         pendingEvent.event = event;
    226         pendingEvent.policyFlags = policyFlags;
    227         pendingEvent.referenceCount = 0;
    228         pendingEvent.handled = false;
    229         return pendingEvent;
    230     }
    231 
    232     private static PendingKeyEvent removeEventFromListLocked(
    233             List<PendingKeyEvent> listOfEvents, int sequence) {
    234         /* In normal operation, the event should be first */
    235         for (int i = 0; i < listOfEvents.size(); i++) {
    236             PendingKeyEvent pendingKeyEvent = listOfEvents.get(i);
    237             if (pendingKeyEvent.event.getSequenceNumber() == sequence) {
    238                     /*
    239                      * Removing the first element of the ArrayList can be slow if there are a lot
    240                      * of events backed up, but for a handful of events it's better than incurring
    241                      * the fixed overhead of LinkedList. An ArrayList optimized for removing the
    242                      * first element (by treating the underlying array as a circular buffer) would
    243                      * be ideal.
    244                      */
    245                 listOfEvents.remove(pendingKeyEvent);
    246                 return pendingKeyEvent;
    247             }
    248         }
    249         return null;
    250     }
    251 
    252     /**
    253      * @param pendingEvent The event whose reference count should be decreased
    254      * @return {@code true} if the event was release, {@code false} if not.
    255      */
    256     private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
    257         if (--pendingEvent.referenceCount > 0) {
    258             return false;
    259         }
    260         mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
    261         if (!pendingEvent.handled) {
    262                 /* Pass event to input filter */
    263             if (DEBUG) {
    264                 Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event);
    265             }
    266             if (mSentEventsVerifier != null) {
    267                 mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
    268             }
    269             int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER;
    270             mHandlerToSendKeyEventsToInputFilter
    271                     .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event)
    272                     .sendToTarget();
    273         } else {
    274             pendingEvent.event.recycle();
    275         }
    276         mPendingEventPool.release(pendingEvent);
    277         return true;
    278     }
    279 
    280     private static final class PendingKeyEvent {
    281         /* Event and policyFlag provided in notifyKeyEventLocked */
    282         KeyEvent event;
    283         int policyFlags;
    284         /*
    285          * The referenceCount optimizes the process of determining the number of services
    286          * still holding a KeyEvent. It must be equal to the number of times the PendingEvent
    287          * appears in mPendingEventsMap, or PendingEvents will leak.
    288          */
    289         int referenceCount;
    290         /* Whether or not at least one service had handled this event */
    291         boolean handled;
    292     }
    293 
    294     public interface KeyEventFilter {
    295         /**
    296          * Filter a key event if possible
    297          *
    298          * @param event The event to filter
    299          * @param sequenceNumber The sequence number of the event
    300          *
    301          * @return {@code true} if the filter is active and will call back with status.
    302          * {@code false} if the filter is not active and will ignore the event
    303          */
    304         boolean onKeyEvent(KeyEvent event, int sequenceNumber);
    305     }
    306 }
    307