/* ** Copyright 2015, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package com.android.server.accessibility; import android.os.Binder; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.util.ArrayMap; import android.util.Pools; import android.util.Pools.Pool; import android.util.Slog; import android.view.InputEventConsistencyVerifier; import android.view.KeyEvent; import android.view.WindowManagerPolicy; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Dispatcher to send KeyEvents to all accessibility services that are able to process them. * Events that are handled by one or more services are consumed. Events that are not processed * by any service (or time out before a service reports them as handled) are passed along to * the rest of the system. * * The class assumes that services report their return values in order, which is valid because * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so * don't see the N+1th event until they have processed the Nth event. */ public class KeyEventDispatcher implements Handler.Callback { // Debugging private static final String LOG_TAG = "KeyEventDispatcher"; private static final boolean DEBUG = false; /* KeyEvents must be processed in this time interval */ private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500; public static final int MSG_ON_KEY_EVENT_TIMEOUT = 1; private static final int MAX_POOL_SIZE = 10; private final Pool mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE); private final Object mLock; /* * Track events sent to each filter. If a KeyEvent is to be sent to at least one service, * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in * the list for each service its KeyEvent is sent to. It is removed from the list when * the service calls setOnKeyEventResult, or when we time out waiting for the service to * respond. */ private final Map> mPendingEventsMap = new ArrayMap<>(); private final InputEventConsistencyVerifier mSentEventsVerifier; private final Handler mHandlerToSendKeyEventsToInputFilter; private final int mMessageTypeForSendKeyEvent; private final PowerManager mPowerManager; private Handler mKeyEventTimeoutHandler; /** * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s * that have not been handled by any accessibility service. * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the * message that carries a {@code KeyEvent} to be sent to the input filter * @param lock The lock used for all synchronization in this package. This lock must be held * when calling {@code notifyKeyEventLocked} * @param powerManager The power manager to alert to user activity if a KeyEvent is processed * by a service */ public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter, int messageTypeForSendKeyEvent, Object lock, PowerManager powerManager) { if (InputEventConsistencyVerifier.isInstrumentationEnabled()) { mSentEventsVerifier = new InputEventConsistencyVerifier( this, 0, KeyEventDispatcher.class.getSimpleName()); } else { mSentEventsVerifier = null; } mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter; mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent; mKeyEventTimeoutHandler = new Handler(handlerToSendKeyEventsToInputFilter.getLooper(), this); mLock = lock; mPowerManager = powerManager; } /** * See above for most params * @param timeoutHandler Specify a handler to use for handling timeouts. This internal state is * exposed for testing. */ public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter, int messageTypeForSendKeyEvent, Object lock, PowerManager powerManager, Handler timeoutHandler) { this(handlerToSendKeyEventsToInputFilter, messageTypeForSendKeyEvent, lock, powerManager); mKeyEventTimeoutHandler = timeoutHandler; } /** * Notify that a new KeyEvent is available to accessibility services. Must be called with the * lock used to construct this object held. The keyEventFilters list must also be protected * by the lock. * * @param event The new key event * @param policyFlags Flags for the event * @param keyEventFilters A list of keyEventFilters that should be considered for processing * this event * * @return {@code true} if the event was passed to at least one AccessibilityService, * {@code false} otherwise. */ // TODO: The locking policy for keyEventFilters needs some thought. public boolean notifyKeyEventLocked( KeyEvent event, int policyFlags, List keyEventFilters) { PendingKeyEvent pendingKeyEvent = null; KeyEvent localClone = KeyEvent.obtain(event); for (int i = 0; i < keyEventFilters.size(); i++) { KeyEventFilter keyEventFilter = keyEventFilters.get(i); if (keyEventFilter.onKeyEvent(localClone, localClone.getSequenceNumber())) { if (pendingKeyEvent == null) { pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags); } ArrayList pendingEventList = mPendingEventsMap.get(keyEventFilter); if (pendingEventList == null) { pendingEventList = new ArrayList<>(); mPendingEventsMap.put(keyEventFilter, pendingEventList); } pendingEventList.add(pendingKeyEvent); pendingKeyEvent.referenceCount++; } } if (pendingKeyEvent == null) { localClone.recycle(); return false; } Message message = mKeyEventTimeoutHandler.obtainMessage( MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent); mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS); return true; } /** * Set the result from onKeyEvent from one service. * * @param keyEventFilter The filter setting the result * @param handled {@code true} if the service handled the {@code KeyEvent} * @param sequence The sequence number of the {@code KeyEvent} */ public void setOnKeyEventResult(KeyEventFilter keyEventFilter, boolean handled, int sequence) { synchronized (mLock) { PendingKeyEvent pendingEvent = removeEventFromListLocked(mPendingEventsMap.get(keyEventFilter), sequence); if (pendingEvent != null) { if (handled && !pendingEvent.handled) { pendingEvent.handled = handled; final long identity = Binder.clearCallingIdentity(); try { mPowerManager.userActivity(pendingEvent.event.getEventTime(), PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0); } finally { Binder.restoreCallingIdentity(identity); } } removeReferenceToPendingEventLocked(pendingEvent); } } } /** * Flush all pending key events for a service, treating all of them as unhandled * * @param keyEventFilter The filter for which to flush events */ public void flush(KeyEventFilter keyEventFilter) { synchronized (mLock) { List pendingEvents = mPendingEventsMap.get(keyEventFilter); if (pendingEvents != null) { for (int i = 0; i < pendingEvents.size(); i++) { PendingKeyEvent pendingEvent = pendingEvents.get(i); removeReferenceToPendingEventLocked(pendingEvent); } mPendingEventsMap.remove(keyEventFilter); } } } @Override public boolean handleMessage(Message message) { if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) { Slog.w(LOG_TAG, "Unknown message: " + message.what); return false; } PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj; synchronized (mLock) { for (ArrayList listForService : mPendingEventsMap.values()) { if (listForService.remove(pendingKeyEvent)) { if(removeReferenceToPendingEventLocked(pendingKeyEvent)) { break; } } } } return true; } private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) { PendingKeyEvent pendingEvent = mPendingEventPool.acquire(); if (pendingEvent == null) { pendingEvent = new PendingKeyEvent(); } pendingEvent.event = event; pendingEvent.policyFlags = policyFlags; pendingEvent.referenceCount = 0; pendingEvent.handled = false; return pendingEvent; } private static PendingKeyEvent removeEventFromListLocked( List listOfEvents, int sequence) { /* In normal operation, the event should be first */ for (int i = 0; i < listOfEvents.size(); i++) { PendingKeyEvent pendingKeyEvent = listOfEvents.get(i); if (pendingKeyEvent.event.getSequenceNumber() == sequence) { /* * Removing the first element of the ArrayList can be slow if there are a lot * of events backed up, but for a handful of events it's better than incurring * the fixed overhead of LinkedList. An ArrayList optimized for removing the * first element (by treating the underlying array as a circular buffer) would * be ideal. */ listOfEvents.remove(pendingKeyEvent); return pendingKeyEvent; } } return null; } /** * @param pendingEvent The event whose reference count should be decreased * @return {@code true} if the event was release, {@code false} if not. */ private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) { if (--pendingEvent.referenceCount > 0) { return false; } mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent); if (!pendingEvent.handled) { /* Pass event to input filter */ if (DEBUG) { Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event); } if (mSentEventsVerifier != null) { mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0); } int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER; mHandlerToSendKeyEventsToInputFilter .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event) .sendToTarget(); } else { pendingEvent.event.recycle(); } mPendingEventPool.release(pendingEvent); return true; } private static final class PendingKeyEvent { /* Event and policyFlag provided in notifyKeyEventLocked */ KeyEvent event; int policyFlags; /* * The referenceCount optimizes the process of determining the number of services * still holding a KeyEvent. It must be equal to the number of times the PendingEvent * appears in mPendingEventsMap, or PendingEvents will leak. */ int referenceCount; /* Whether or not at least one service had handled this event */ boolean handled; } public interface KeyEventFilter { /** * Filter a key event if possible * * @param event The event to filter * @param sequenceNumber The sequence number of the event * * @return {@code true} if the filter is active and will call back with status. * {@code false} if the filter is not active and will ignore the event */ boolean onKeyEvent(KeyEvent event, int sequenceNumber); } }