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