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