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
17package com.android.server.accessibility;
18
19import android.os.Binder;
20import android.os.Handler;
21import android.os.Message;
22import android.os.PowerManager;
23import android.util.ArrayMap;
24import android.util.Pools;
25import android.util.Pools.Pool;
26import android.util.Slog;
27import android.view.InputEventConsistencyVerifier;
28import android.view.KeyEvent;
29import android.view.WindowManagerPolicy;
30
31import java.util.ArrayList;
32import java.util.List;
33import 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 */
45public 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