| /* |
| ** 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<PendingKeyEvent> 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<KeyEventFilter, ArrayList<PendingKeyEvent>> 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<? extends KeyEventFilter> 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<PendingKeyEvent> 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<PendingKeyEvent> 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<PendingKeyEvent> 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<PendingKeyEvent> 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); |
| } |
| } |