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 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 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 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 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 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 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 return; 223 } 224 mKeyEventSequenceStarted = true; 225 } 226 mAms.notifyKeyEvent(event, policyFlags); 227 } 228 229 private void scheduleProcessBatchedEvents() { 230 mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, 231 mProcessBatchedEventsRunnable, null); 232 } 233 234 private void batchMotionEvent(MotionEvent event, int policyFlags) { 235 if (DEBUG) { 236 Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags); 237 } 238 if (mEventQueue == null) { 239 mEventQueue = MotionEventHolder.obtain(event, policyFlags); 240 scheduleProcessBatchedEvents(); 241 return; 242 } 243 if (mEventQueue.event.addBatch(event)) { 244 return; 245 } 246 MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags); 247 holder.next = mEventQueue; 248 mEventQueue.previous = holder; 249 mEventQueue = holder; 250 } 251 252 private void processBatchedEvents(long frameNanos) { 253 MotionEventHolder current = mEventQueue; 254 while (current.next != null) { 255 current = current.next; 256 } 257 while (true) { 258 if (current == null) { 259 mEventQueue = null; 260 break; 261 } 262 if (current.event.getEventTimeNano() >= frameNanos) { 263 // Finished with this choreographer frame. Do the rest on the next one. 264 current.next = null; 265 break; 266 } 267 handleMotionEvent(current.event, current.policyFlags); 268 MotionEventHolder prior = current; 269 current = current.previous; 270 prior.recycle(); 271 } 272 } 273 274 private void handleMotionEvent(MotionEvent event, int policyFlags) { 275 if (DEBUG) { 276 Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags); 277 } 278 // Since we do batch processing it is possible that by the time the 279 // next batch is processed the event handle had been set to null. 280 if (mEventHandler != null) { 281 mPm.userActivity(event.getEventTime(), false); 282 MotionEvent transformedEvent = MotionEvent.obtain(event); 283 mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); 284 transformedEvent.recycle(); 285 } 286 } 287 288 @Override 289 public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, 290 int policyFlags) { 291 sendInputEvent(transformedEvent, policyFlags); 292 } 293 294 @Override 295 public void onAccessibilityEvent(AccessibilityEvent event) { 296 // TODO Implement this to inject the accessibility event 297 // into the accessibility manager service similarly 298 // to how this is done for input events. 299 } 300 301 @Override 302 public void setNext(EventStreamTransformation sink) { 303 /* do nothing */ 304 } 305 306 @Override 307 public void clear() { 308 /* do nothing */ 309 } 310 311 void setEnabledFeatures(int enabledFeatures) { 312 if (mEnabledFeatures == enabledFeatures) { 313 return; 314 } 315 if (mInstalled) { 316 disableFeatures(); 317 } 318 mEnabledFeatures = enabledFeatures; 319 if (mInstalled) { 320 enableFeatures(); 321 } 322 } 323 324 void notifyAccessibilityEvent(AccessibilityEvent event) { 325 if (mEventHandler != null) { 326 mEventHandler.onAccessibilityEvent(event); 327 } 328 } 329 330 private void enableFeatures() { 331 mMotionEventSequenceStarted = false; 332 mHoverEventSequenceStarted = false; 333 if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { 334 mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext, 335 Display.DEFAULT_DISPLAY, mAms); 336 mEventHandler.setNext(this); 337 } 338 if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { 339 mTouchExplorer = new TouchExplorer(mContext, mAms); 340 mTouchExplorer.setNext(this); 341 if (mEventHandler != null) { 342 mEventHandler.setNext(mTouchExplorer); 343 } else { 344 mEventHandler = mTouchExplorer; 345 } 346 } 347 if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { 348 mFilterKeyEvents = true; 349 } 350 } 351 352 void disableFeatures() { 353 if (mTouchExplorer != null) { 354 mTouchExplorer.clear(); 355 mTouchExplorer.onDestroy(); 356 mTouchExplorer = null; 357 } 358 if (mScreenMagnifier != null) { 359 mScreenMagnifier.clear(); 360 mScreenMagnifier.onDestroy(); 361 mScreenMagnifier = null; 362 } 363 mEventHandler = null; 364 mKeyEventSequenceStarted = false; 365 mMotionEventSequenceStarted = false; 366 mHoverEventSequenceStarted = false; 367 mFilterKeyEvents = false; 368 } 369 370 @Override 371 public void onDestroy() { 372 /* ignore */ 373 } 374 375 private static class MotionEventHolder { 376 private static final int MAX_POOL_SIZE = 32; 377 private static final SimplePool<MotionEventHolder> sPool = 378 new SimplePool<MotionEventHolder>(MAX_POOL_SIZE); 379 380 public int policyFlags; 381 public MotionEvent event; 382 public MotionEventHolder next; 383 public MotionEventHolder previous; 384 385 public static MotionEventHolder obtain(MotionEvent event, int policyFlags) { 386 MotionEventHolder holder = sPool.acquire(); 387 if (holder == null) { 388 holder = new MotionEventHolder(); 389 } 390 holder.event = MotionEvent.obtain(event); 391 holder.policyFlags = policyFlags; 392 return holder; 393 } 394 395 public void recycle() { 396 event.recycle(); 397 event = null; 398 policyFlags = 0; 399 next = null; 400 previous = null; 401 sPool.release(this); 402 } 403 } 404 } 405