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.view.Choreographer; 24import android.view.Display; 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 #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 super.onInputEvent(event, policyFlags); 223 return; 224 } 225 mKeyEventSequenceStarted = true; 226 } 227 mAms.notifyKeyEvent(event, policyFlags); 228 } 229 230 private void scheduleProcessBatchedEvents() { 231 mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, 232 mProcessBatchedEventsRunnable, null); 233 } 234 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 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 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 290 public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, 291 int policyFlags) { 292 sendInputEvent(transformedEvent, policyFlags); 293 } 294 295 @Override 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 303 public void setNext(EventStreamTransformation sink) { 304 /* do nothing */ 305 } 306 307 @Override 308 public void clear() { 309 /* do nothing */ 310 } 311 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 325 void notifyAccessibilityEvent(AccessibilityEvent event) { 326 if (mEventHandler != null) { 327 mEventHandler.onAccessibilityEvent(event); 328 } 329 } 330 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 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 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 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 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