1/*
2 * Copyright (C) 2011 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.content.Context;
20import android.os.PowerManager;
21import android.util.Pools.SimplePool;
22import android.util.Slog;
23import android.util.SparseBooleanArray;
24import android.view.Choreographer;
25import android.view.InputDevice;
26import android.view.InputEvent;
27import android.view.InputFilter;
28import android.view.KeyEvent;
29import android.view.MotionEvent;
30import android.view.WindowManagerPolicy;
31import android.view.accessibility.AccessibilityEvent;
32
33/**
34 * This class is an input filter for implementing accessibility features such
35 * as display magnification and explore by touch.
36 *
37 * NOTE: This class has to be created and poked only from the main thread.
38 */
39class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
40
41    private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
42
43    private static final boolean DEBUG = false;
44
45    /**
46     * Flag for enabling the screen magnification feature.
47     *
48     * @see #setUserAndEnabledFeatures(int, int)
49     */
50    static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
51
52    /**
53     * Flag for enabling the touch exploration feature.
54     *
55     * @see #setUserAndEnabledFeatures(int, int)
56     */
57    static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002;
58
59    /**
60     * Flag for enabling the filtering key events feature.
61     *
62     * @see #setUserAndEnabledFeatures(int, int)
63     */
64    static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004;
65
66    /**
67     * Flag for enabling "Automatically click on mouse stop" feature.
68     *
69     * @see #setUserAndEnabledFeatures(int, int)
70     */
71    static final int FLAG_FEATURE_AUTOCLICK = 0x00000008;
72
73    /**
74     * Flag for enabling motion event injection.
75     *
76     * @see #setUserAndEnabledFeatures(int, int)
77     */
78    static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010;
79
80    /**
81     * Flag for enabling the feature to control the screen magnifier. If
82     * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored
83     * as the screen magnifier feature performs a super set of the work
84     * performed by this feature.
85     *
86     * @see #setUserAndEnabledFeatures(int, int)
87     */
88    static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020;
89
90    private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
91        @Override
92        public void run() {
93            final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
94            if (DEBUG) {
95                Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos);
96            }
97            processBatchedEvents(frameTimeNanos);
98            if (DEBUG) {
99                Slog.i(TAG, "End batch processing.");
100            }
101            if (mEventQueue != null) {
102                scheduleProcessBatchedEvents();
103            }
104        }
105    };
106
107    private final Context mContext;
108
109    private final PowerManager mPm;
110
111    private final AccessibilityManagerService mAms;
112
113    private final Choreographer mChoreographer;
114
115    private boolean mInstalled;
116
117    private int mUserId;
118
119    private int mEnabledFeatures;
120
121    private TouchExplorer mTouchExplorer;
122
123    private MagnificationGestureHandler mMagnificationGestureHandler;
124
125    private MotionEventInjector mMotionEventInjector;
126
127    private AutoclickController mAutoclickController;
128
129    private KeyboardInterceptor mKeyboardInterceptor;
130
131    private EventStreamTransformation mEventHandler;
132
133    private MotionEventHolder mEventQueue;
134
135    private EventStreamState mMouseStreamState;
136
137    private EventStreamState mTouchScreenStreamState;
138
139    private EventStreamState mKeyboardStreamState;
140
141    AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
142        super(context.getMainLooper());
143        mContext = context;
144        mAms = service;
145        mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
146        mChoreographer = Choreographer.getInstance();
147    }
148
149    @Override
150    public void onInstalled() {
151        if (DEBUG) {
152            Slog.d(TAG, "Accessibility input filter installed.");
153        }
154        mInstalled = true;
155        disableFeatures();
156        enableFeatures();
157        super.onInstalled();
158    }
159
160    @Override
161    public void onUninstalled() {
162        if (DEBUG) {
163            Slog.d(TAG, "Accessibility input filter uninstalled.");
164        }
165        mInstalled = false;
166        disableFeatures();
167        super.onUninstalled();
168    }
169
170    @Override
171    public void onInputEvent(InputEvent event, int policyFlags) {
172        if (DEBUG) {
173            Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
174                    + Integer.toHexString(policyFlags));
175        }
176
177        if (mEventHandler == null) {
178            super.onInputEvent(event, policyFlags);
179            return;
180        }
181
182        EventStreamState state = getEventStreamState(event);
183        if (state == null) {
184            super.onInputEvent(event, policyFlags);
185            return;
186        }
187
188        int eventSource = event.getSource();
189        if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
190            state.reset();
191            mEventHandler.clearEvents(eventSource);
192            super.onInputEvent(event, policyFlags);
193            return;
194        }
195
196        if (state.updateDeviceId(event.getDeviceId())) {
197            mEventHandler.clearEvents(eventSource);
198        }
199
200        if (!state.deviceIdValid()) {
201            super.onInputEvent(event, policyFlags);
202            return;
203        }
204
205        if (event instanceof MotionEvent) {
206            MotionEvent motionEvent = (MotionEvent) event;
207            processMotionEvent(state, motionEvent, policyFlags);
208        } else if (event instanceof KeyEvent) {
209            KeyEvent keyEvent = (KeyEvent) event;
210            processKeyEvent(state, keyEvent, policyFlags);
211        }
212    }
213
214    /**
215     * Gets current event stream state associated with an input event.
216     * @return The event stream state that should be used for the event. Null if the event should
217     *     not be handled by #AccessibilityInputFilter.
218     */
219    private EventStreamState getEventStreamState(InputEvent event) {
220        if (event instanceof MotionEvent) {
221          if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
222              if (mTouchScreenStreamState == null) {
223                  mTouchScreenStreamState = new TouchScreenEventStreamState();
224              }
225              return mTouchScreenStreamState;
226          }
227          if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
228              if (mMouseStreamState == null) {
229                  mMouseStreamState = new MouseEventStreamState();
230              }
231              return mMouseStreamState;
232          }
233        } else if (event instanceof KeyEvent) {
234          if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
235              if (mKeyboardStreamState == null) {
236                  mKeyboardStreamState = new KeyboardEventStreamState();
237              }
238              return mKeyboardStreamState;
239          }
240        }
241        return null;
242    }
243
244    private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
245        if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
246            super.onInputEvent(event, policyFlags);
247            return;
248        }
249
250        if (!state.shouldProcessMotionEvent(event)) {
251            return;
252        }
253
254        batchMotionEvent(event, policyFlags);
255    }
256
257    private void processKeyEvent(EventStreamState state, KeyEvent event, int policyFlags) {
258        if (!state.shouldProcessKeyEvent(event)) {
259            return;
260        }
261        mEventHandler.onKeyEvent(event, policyFlags);
262    }
263
264    private void scheduleProcessBatchedEvents() {
265        mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
266                mProcessBatchedEventsRunnable, null);
267    }
268
269    private void batchMotionEvent(MotionEvent event, int policyFlags) {
270        if (DEBUG) {
271            Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
272        }
273        if (mEventQueue == null) {
274            mEventQueue = MotionEventHolder.obtain(event, policyFlags);
275            scheduleProcessBatchedEvents();
276            return;
277        }
278        if (mEventQueue.event.addBatch(event)) {
279            return;
280        }
281        MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
282        holder.next = mEventQueue;
283        mEventQueue.previous = holder;
284        mEventQueue = holder;
285    }
286
287    private void processBatchedEvents(long frameNanos) {
288        MotionEventHolder current = mEventQueue;
289        if (current == null) {
290            return;
291        }
292        while (current.next != null) {
293            current = current.next;
294        }
295        while (true) {
296            if (current == null) {
297                mEventQueue = null;
298                break;
299            }
300            if (current.event.getEventTimeNano() >= frameNanos) {
301                // Finished with this choreographer frame. Do the rest on the next one.
302                current.next = null;
303                break;
304            }
305            handleMotionEvent(current.event, current.policyFlags);
306            MotionEventHolder prior = current;
307            current = current.previous;
308            prior.recycle();
309        }
310    }
311
312    private void handleMotionEvent(MotionEvent event, int policyFlags) {
313        if (DEBUG) {
314            Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
315        }
316        // Since we do batch processing it is possible that by the time the
317        // next batch is processed the event handle had been set to null.
318        if (mEventHandler != null) {
319            mPm.userActivity(event.getEventTime(), false);
320            MotionEvent transformedEvent = MotionEvent.obtain(event);
321            mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
322            transformedEvent.recycle();
323        }
324    }
325
326    @Override
327    public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
328            int policyFlags) {
329        sendInputEvent(transformedEvent, policyFlags);
330    }
331
332    @Override
333    public void onKeyEvent(KeyEvent event, int policyFlags) {
334        sendInputEvent(event, policyFlags);
335    }
336
337    @Override
338    public void onAccessibilityEvent(AccessibilityEvent event) {
339        // TODO Implement this to inject the accessibility event
340        //      into the accessibility manager service similarly
341        //      to how this is done for input events.
342    }
343
344    @Override
345    public void setNext(EventStreamTransformation sink) {
346        /* do nothing */
347    }
348
349    @Override
350    public void clearEvents(int inputSource) {
351        /* do nothing */
352    }
353
354    void setUserAndEnabledFeatures(int userId, int enabledFeatures) {
355        if (mEnabledFeatures == enabledFeatures && mUserId == userId) {
356            return;
357        }
358        if (mInstalled) {
359            disableFeatures();
360        }
361        mUserId = userId;
362        mEnabledFeatures = enabledFeatures;
363        if (mInstalled) {
364            enableFeatures();
365        }
366    }
367
368    void notifyAccessibilityEvent(AccessibilityEvent event) {
369        if (mEventHandler != null) {
370            mEventHandler.onAccessibilityEvent(event);
371        }
372    }
373
374    private void enableFeatures() {
375        resetStreamState();
376
377        if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
378            mAutoclickController = new AutoclickController(mContext, mUserId);
379            addFirstEventHandler(mAutoclickController);
380        }
381
382        if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
383            mTouchExplorer = new TouchExplorer(mContext, mAms);
384            addFirstEventHandler(mTouchExplorer);
385        }
386
387        if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
388                || (mEnabledFeatures  & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
389            final boolean detectControlGestures = (mEnabledFeatures
390                    & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
391            mMagnificationGestureHandler = new MagnificationGestureHandler(
392                    mContext, mAms, detectControlGestures);
393            addFirstEventHandler(mMagnificationGestureHandler);
394        }
395
396        if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
397            mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
398            addFirstEventHandler(mMotionEventInjector);
399            mAms.setMotionEventInjector(mMotionEventInjector);
400        }
401
402        if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
403            mKeyboardInterceptor = new KeyboardInterceptor(mAms);
404            addFirstEventHandler(mKeyboardInterceptor);
405        }
406    }
407
408    /**
409     * Adds an event handler to the event handler chain. The handler is added at the beginning of
410     * the chain.
411     *
412     * @param handler The handler to be added to the event handlers list.
413     */
414    private void addFirstEventHandler(EventStreamTransformation handler) {
415        if (mEventHandler != null) {
416           handler.setNext(mEventHandler);
417        } else {
418            handler.setNext(this);
419        }
420        mEventHandler = handler;
421    }
422
423    private void disableFeatures() {
424        // Give the features a chance to process any batched events so we'll keep a consistent
425        // event stream
426        processBatchedEvents(Long.MAX_VALUE);
427        if (mMotionEventInjector != null) {
428            mAms.setMotionEventInjector(null);
429            mMotionEventInjector.onDestroy();
430            mMotionEventInjector = null;
431        }
432        if (mAutoclickController != null) {
433            mAutoclickController.onDestroy();
434            mAutoclickController = null;
435        }
436        if (mTouchExplorer != null) {
437            mTouchExplorer.onDestroy();
438            mTouchExplorer = null;
439        }
440        if (mMagnificationGestureHandler != null) {
441            mMagnificationGestureHandler.onDestroy();
442            mMagnificationGestureHandler = null;
443        }
444        if (mKeyboardInterceptor != null) {
445            mKeyboardInterceptor.onDestroy();
446            mKeyboardInterceptor = null;
447        }
448
449        mEventHandler = null;
450        resetStreamState();
451    }
452
453    void resetStreamState() {
454        if (mTouchScreenStreamState != null) {
455            mTouchScreenStreamState.reset();
456        }
457        if (mMouseStreamState != null) {
458            mMouseStreamState.reset();
459        }
460        if (mKeyboardStreamState != null) {
461            mKeyboardStreamState.reset();
462        }
463    }
464
465    @Override
466    public void onDestroy() {
467        /* ignore */
468    }
469
470    private static class MotionEventHolder {
471        private static final int MAX_POOL_SIZE = 32;
472        private static final SimplePool<MotionEventHolder> sPool =
473                new SimplePool<MotionEventHolder>(MAX_POOL_SIZE);
474
475        public int policyFlags;
476        public MotionEvent event;
477        public MotionEventHolder next;
478        public MotionEventHolder previous;
479
480        public static MotionEventHolder obtain(MotionEvent event, int policyFlags) {
481            MotionEventHolder holder = sPool.acquire();
482            if (holder == null) {
483                holder = new MotionEventHolder();
484            }
485            holder.event = MotionEvent.obtain(event);
486            holder.policyFlags = policyFlags;
487            return holder;
488        }
489
490        public void recycle() {
491            event.recycle();
492            event = null;
493            policyFlags = 0;
494            next = null;
495            previous = null;
496            sPool.release(this);
497        }
498    }
499
500    /**
501     * Keeps state of event streams observed for an input device with a certain source.
502     * Provides information about whether motion and key events should be processed by accessibility
503     * #EventStreamTransformations. Base implementation describes behaviour for event sources that
504     * whose events should not be handled by a11y event stream transformations.
505     */
506    private static class EventStreamState {
507        private int mDeviceId;
508
509        EventStreamState() {
510            mDeviceId = -1;
511        }
512
513        /**
514         * Updates the ID of the device associated with the state. If the ID changes, resets
515         * internal state.
516         *
517         * @param deviceId Updated input device ID.
518         * @return Whether the device ID has changed.
519         */
520        public boolean updateDeviceId(int deviceId) {
521            if (mDeviceId == deviceId) {
522                return false;
523            }
524            // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
525            reset();
526            mDeviceId = deviceId;
527            return true;
528        }
529
530        /**
531         * @return Whether device ID is valid.
532         */
533        public boolean deviceIdValid() {
534            return mDeviceId >= 0;
535        }
536
537        /**
538         * Resets the event stream state.
539         */
540        public void reset() {
541            mDeviceId = -1;
542        }
543
544        /**
545         * @return Whether scroll events for device should be handled by event transformations.
546         */
547        public boolean shouldProcessScroll() {
548            return false;
549        }
550
551        /**
552         * @param event An observed motion event.
553         * @return Whether the event should be handled by event transformations.
554         */
555        public boolean shouldProcessMotionEvent(MotionEvent event) {
556            return false;
557        }
558
559        /**
560         * @param event An observed key event.
561         * @return Whether the event should be handled by event transformations.
562         */
563        public boolean shouldProcessKeyEvent(KeyEvent event) {
564            return false;
565        }
566    }
567
568    /**
569     * Keeps state of stream of events from a mouse device.
570     */
571    private static class MouseEventStreamState extends EventStreamState {
572        private boolean mMotionSequenceStarted;
573
574        public MouseEventStreamState() {
575            reset();
576        }
577
578        @Override
579        final public void reset() {
580            super.reset();
581            mMotionSequenceStarted = false;
582        }
583
584        @Override
585        final public boolean shouldProcessScroll() {
586            return true;
587        }
588
589        @Override
590        final public boolean shouldProcessMotionEvent(MotionEvent event) {
591            if (mMotionSequenceStarted) {
592                return true;
593            }
594            // Wait for down or move event to start processing mouse events.
595            int action = event.getActionMasked();
596            mMotionSequenceStarted =
597                    action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_HOVER_MOVE;
598            return mMotionSequenceStarted;
599        }
600    }
601
602    /**
603     * Keeps state of stream of events from a touch screen device.
604     */
605    private static class TouchScreenEventStreamState extends EventStreamState {
606        private boolean mTouchSequenceStarted;
607        private boolean mHoverSequenceStarted;
608
609        public TouchScreenEventStreamState() {
610            reset();
611        }
612
613        @Override
614        final public void reset() {
615            super.reset();
616            mTouchSequenceStarted = false;
617            mHoverSequenceStarted = false;
618        }
619
620        @Override
621        final public boolean shouldProcessMotionEvent(MotionEvent event) {
622            // Wait for a down touch event to start processing.
623            if (event.isTouchEvent()) {
624                if (mTouchSequenceStarted) {
625                    return true;
626                }
627                mTouchSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_DOWN;
628                return mTouchSequenceStarted;
629            }
630
631            // Wait for an enter hover event to start processing.
632            if (mHoverSequenceStarted) {
633                return true;
634            }
635            mHoverSequenceStarted = event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER;
636            return mHoverSequenceStarted;
637        }
638    }
639
640    /**
641     * Keeps state of streams of events from all keyboard devices.
642     */
643    private static class KeyboardEventStreamState extends EventStreamState {
644        private SparseBooleanArray mEventSequenceStartedMap = new SparseBooleanArray();
645
646        public KeyboardEventStreamState() {
647            reset();
648        }
649
650        @Override
651        final public void reset() {
652            super.reset();
653            mEventSequenceStartedMap.clear();
654        }
655
656        /*
657         * Key events from different devices may be interleaved. For example, the volume up and
658         * down keys can come from different device IDs.
659         */
660        @Override
661        public boolean updateDeviceId(int deviceId) {
662            return false;
663        }
664
665        // We manage all device ids simultaneously; there is no concept of validity.
666        @Override
667        public boolean deviceIdValid() {
668            return true;
669        }
670
671
672        @Override
673        final public boolean shouldProcessKeyEvent(KeyEvent event) {
674            // For each keyboard device, wait for a down event from a device to start processing
675            int deviceId = event.getDeviceId();
676            if (mEventSequenceStartedMap.get(deviceId, false)) {
677                return true;
678            }
679            boolean shouldProcess = event.getAction() == KeyEvent.ACTION_DOWN;
680            mEventSequenceStartedMap.put(deviceId, shouldProcess);
681            return shouldProcess;
682        }
683    }
684}
685