• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.accessibility;
18 
19 import android.content.Context;
20 import android.os.PowerManager;
21 import android.util.Pools.SimplePool;
22 import android.util.Slog;
23 import android.view.Choreographer;
24 import android.view.Display;
25 import android.view.InputDevice;
26 import android.view.InputEvent;
27 import android.view.InputFilter;
28 import android.view.KeyEvent;
29 import android.view.MotionEvent;
30 import android.view.WindowManagerPolicy;
31 import 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  */
39 class 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 #setEnabledFeatures(int)
49      */
50     static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
51 
52     /**
53      * Flag for enabling the touch exploration feature.
54      *
55      * @see #setEnabledFeatures(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 #setEnabledFeatures(int)
63      */
64     static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004;
65 
66     private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
67         @Override
68         public void run() {
69             final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
70             if (DEBUG) {
71                 Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos);
72             }
73             processBatchedEvents(frameTimeNanos);
74             if (DEBUG) {
75                 Slog.i(TAG, "End batch processing.");
76             }
77             if (mEventQueue != null) {
78                 scheduleProcessBatchedEvents();
79             }
80         }
81     };
82 
83     private final Context mContext;
84 
85     private final PowerManager mPm;
86 
87     private final AccessibilityManagerService mAms;
88 
89     private final Choreographer mChoreographer;
90 
91     private int mCurrentTouchDeviceId;
92 
93     private boolean mInstalled;
94 
95     private int mEnabledFeatures;
96 
97     private TouchExplorer mTouchExplorer;
98 
99     private ScreenMagnifier mScreenMagnifier;
100 
101     private EventStreamTransformation mEventHandler;
102 
103     private MotionEventHolder mEventQueue;
104 
105     private boolean mMotionEventSequenceStarted;
106 
107     private boolean mHoverEventSequenceStarted;
108 
109     private boolean mKeyEventSequenceStarted;
110 
111     private boolean mFilterKeyEvents;
112 
AccessibilityInputFilter(Context context, AccessibilityManagerService service)113     AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
114         super(context.getMainLooper());
115         mContext = context;
116         mAms = service;
117         mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
118         mChoreographer = Choreographer.getInstance();
119     }
120 
121     @Override
onInstalled()122     public void onInstalled() {
123         if (DEBUG) {
124             Slog.d(TAG, "Accessibility input filter installed.");
125         }
126         mInstalled = true;
127         disableFeatures();
128         enableFeatures();
129         super.onInstalled();
130     }
131 
132     @Override
onUninstalled()133     public void onUninstalled() {
134         if (DEBUG) {
135             Slog.d(TAG, "Accessibility input filter uninstalled.");
136         }
137         mInstalled = false;
138         disableFeatures();
139         super.onUninstalled();
140     }
141 
142     @Override
onInputEvent(InputEvent event, int policyFlags)143     public void onInputEvent(InputEvent event, int policyFlags) {
144         if (DEBUG) {
145             Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
146                     + Integer.toHexString(policyFlags));
147         }
148         if (event instanceof MotionEvent
149                 && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
150             MotionEvent motionEvent = (MotionEvent) event;
151             onMotionEvent(motionEvent, policyFlags);
152         } else if (event instanceof KeyEvent
153                 && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
154             KeyEvent keyEvent = (KeyEvent) event;
155             onKeyEvent(keyEvent, policyFlags);
156         } else {
157             super.onInputEvent(event, policyFlags);
158         }
159     }
160 
onMotionEvent(MotionEvent event, int policyFlags)161     private void onMotionEvent(MotionEvent event, int policyFlags) {
162         if (mEventHandler == null) {
163             super.onInputEvent(event, policyFlags);
164             return;
165         }
166         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
167             mMotionEventSequenceStarted = false;
168             mHoverEventSequenceStarted = false;
169             mEventHandler.clear();
170             super.onInputEvent(event, policyFlags);
171             return;
172         }
173         final int deviceId = event.getDeviceId();
174         if (mCurrentTouchDeviceId != deviceId) {
175             mCurrentTouchDeviceId = deviceId;
176             mMotionEventSequenceStarted = false;
177             mHoverEventSequenceStarted = false;
178             mEventHandler.clear();
179         }
180         if (mCurrentTouchDeviceId < 0) {
181             super.onInputEvent(event, policyFlags);
182             return;
183         }
184         // We do not handle scroll events.
185         if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
186             super.onInputEvent(event, policyFlags);
187             return;
188         }
189         // Wait for a down touch event to start processing.
190         if (event.isTouchEvent()) {
191             if (!mMotionEventSequenceStarted) {
192                 if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
193                     return;
194                 }
195                 mMotionEventSequenceStarted = true;
196             }
197         } else {
198             // Wait for an enter hover event to start processing.
199             if (!mHoverEventSequenceStarted) {
200                 if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
201                     return;
202                 }
203                 mHoverEventSequenceStarted = true;
204             }
205         }
206         batchMotionEvent((MotionEvent) event, policyFlags);
207     }
208 
onKeyEvent(KeyEvent event, int policyFlags)209     private void onKeyEvent(KeyEvent event, int policyFlags) {
210         if (!mFilterKeyEvents) {
211             super.onInputEvent(event, policyFlags);
212             return;
213         }
214         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
215             mKeyEventSequenceStarted = false;
216             super.onInputEvent(event, policyFlags);
217             return;
218         }
219         // Wait for a down key event to start processing.
220         if (!mKeyEventSequenceStarted) {
221             if (event.getAction() != KeyEvent.ACTION_DOWN) {
222                 super.onInputEvent(event, policyFlags);
223                 return;
224             }
225             mKeyEventSequenceStarted = true;
226         }
227         mAms.notifyKeyEvent(event, policyFlags);
228     }
229 
scheduleProcessBatchedEvents()230     private void scheduleProcessBatchedEvents() {
231         mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
232                 mProcessBatchedEventsRunnable, null);
233     }
234 
batchMotionEvent(MotionEvent event, int policyFlags)235     private void batchMotionEvent(MotionEvent event, int policyFlags) {
236         if (DEBUG) {
237             Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
238         }
239         if (mEventQueue == null) {
240             mEventQueue = MotionEventHolder.obtain(event, policyFlags);
241             scheduleProcessBatchedEvents();
242             return;
243         }
244         if (mEventQueue.event.addBatch(event)) {
245             return;
246         }
247         MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
248         holder.next = mEventQueue;
249         mEventQueue.previous = holder;
250         mEventQueue = holder;
251     }
252 
processBatchedEvents(long frameNanos)253     private void processBatchedEvents(long frameNanos) {
254         MotionEventHolder current = mEventQueue;
255         while (current.next != null) {
256             current = current.next;
257         }
258         while (true) {
259             if (current == null) {
260                 mEventQueue = null;
261                 break;
262             }
263             if (current.event.getEventTimeNano() >= frameNanos) {
264                 // Finished with this choreographer frame. Do the rest on the next one.
265                 current.next = null;
266                 break;
267             }
268             handleMotionEvent(current.event, current.policyFlags);
269             MotionEventHolder prior = current;
270             current = current.previous;
271             prior.recycle();
272         }
273     }
274 
handleMotionEvent(MotionEvent event, int policyFlags)275     private void handleMotionEvent(MotionEvent event, int policyFlags) {
276         if (DEBUG) {
277             Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
278         }
279         // Since we do batch processing it is possible that by the time the
280         // next batch is processed the event handle had been set to null.
281         if (mEventHandler != null) {
282             mPm.userActivity(event.getEventTime(), false);
283             MotionEvent transformedEvent = MotionEvent.obtain(event);
284             mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
285             transformedEvent.recycle();
286         }
287     }
288 
289     @Override
onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, int policyFlags)290     public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
291             int policyFlags) {
292         sendInputEvent(transformedEvent, policyFlags);
293     }
294 
295     @Override
onAccessibilityEvent(AccessibilityEvent event)296     public void onAccessibilityEvent(AccessibilityEvent event) {
297         // TODO Implement this to inject the accessibility event
298         //      into the accessibility manager service similarly
299         //      to how this is done for input events.
300     }
301 
302     @Override
setNext(EventStreamTransformation sink)303     public void setNext(EventStreamTransformation sink) {
304         /* do nothing */
305     }
306 
307     @Override
clear()308     public void clear() {
309         /* do nothing */
310     }
311 
setEnabledFeatures(int enabledFeatures)312     void setEnabledFeatures(int enabledFeatures) {
313         if (mEnabledFeatures == enabledFeatures) {
314             return;
315         }
316         if (mInstalled) {
317             disableFeatures();
318         }
319         mEnabledFeatures = enabledFeatures;
320         if (mInstalled) {
321             enableFeatures();
322         }
323     }
324 
notifyAccessibilityEvent(AccessibilityEvent event)325     void notifyAccessibilityEvent(AccessibilityEvent event) {
326         if (mEventHandler != null) {
327             mEventHandler.onAccessibilityEvent(event);
328         }
329     }
330 
enableFeatures()331     private void enableFeatures() {
332         mMotionEventSequenceStarted = false;
333         mHoverEventSequenceStarted = false;
334         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
335             mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext,
336                     Display.DEFAULT_DISPLAY, mAms);
337             mEventHandler.setNext(this);
338         }
339         if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
340             mTouchExplorer = new TouchExplorer(mContext, mAms);
341             mTouchExplorer.setNext(this);
342             if (mEventHandler != null) {
343                 mEventHandler.setNext(mTouchExplorer);
344             } else {
345                 mEventHandler = mTouchExplorer;
346             }
347         }
348         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
349             mFilterKeyEvents = true;
350         }
351     }
352 
disableFeatures()353     void disableFeatures() {
354         if (mTouchExplorer != null) {
355             mTouchExplorer.clear();
356             mTouchExplorer.onDestroy();
357             mTouchExplorer = null;
358         }
359         if (mScreenMagnifier != null) {
360             mScreenMagnifier.clear();
361             mScreenMagnifier.onDestroy();
362             mScreenMagnifier = null;
363         }
364         mEventHandler = null;
365         mKeyEventSequenceStarted = false;
366         mMotionEventSequenceStarted = false;
367         mHoverEventSequenceStarted = false;
368         mFilterKeyEvents = false;
369     }
370 
371     @Override
onDestroy()372     public void onDestroy() {
373         /* ignore */
374     }
375 
376     private static class MotionEventHolder {
377         private static final int MAX_POOL_SIZE = 32;
378         private static final SimplePool<MotionEventHolder> sPool =
379                 new SimplePool<MotionEventHolder>(MAX_POOL_SIZE);
380 
381         public int policyFlags;
382         public MotionEvent event;
383         public MotionEventHolder next;
384         public MotionEventHolder previous;
385 
obtain(MotionEvent event, int policyFlags)386         public static MotionEventHolder obtain(MotionEvent event, int policyFlags) {
387             MotionEventHolder holder = sPool.acquire();
388             if (holder == null) {
389                 holder = new MotionEventHolder();
390             }
391             holder.event = MotionEvent.obtain(event);
392             holder.policyFlags = policyFlags;
393             return holder;
394         }
395 
recycle()396         public void recycle() {
397             event.recycle();
398             event = null;
399             policyFlags = 0;
400             next = null;
401             previous = null;
402             sPool.release(this);
403         }
404     }
405 }
406