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