diff options
8 files changed, 410 insertions, 174 deletions
diff --git a/api/current.txt b/api/current.txt index 1e1f01ca5c11..58391afb7c86 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2074,6 +2074,7 @@ package android.accessibilityservice { method public final android.os.IBinder onBind(android.content.Intent); method protected boolean onGesture(int); method public abstract void onInterrupt(); + method protected boolean onKeyEvent(android.view.KeyEvent); method protected void onServiceConnected(); method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 811b92a5e102..31de98dc1593 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -24,6 +24,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; @@ -348,6 +349,7 @@ public abstract class AccessibilityService extends Service { public void onServiceConnected(); public void onSetConnectionId(int connectionId); public boolean onGesture(int gestureId); + public boolean onKeyEvent(KeyEvent event); } private int mConnectionId; @@ -413,6 +415,32 @@ public abstract class AccessibilityService extends Service { } /** + * Callback that allows an accessibility service to observe the key events + * before they are passed to the rest of the system. This means that the events + * are first delivered here before they are passed to the device policy, the + * input method, or applications. + * <p> + * <strong>Note:</strong> It is important that key events are handled in such + * a way that the event stream that would be passed to the rest of the system + * is well-formed. For example, handling the down event but not the up event + * and vice versa would generate an inconsistent event stream. + * </p> + * <p> + * <strong>Note:</strong> The key events delivered in this method are copies + * and modifying them will have no effect on the events that will be passed + * to the system. This method is intended to perform purely filtering + * functionality. + * <p> + * + * @param event The event to be processed. + * @return If true then the event will be consumed and not delivered to + * applications, otherwise it will be delivered as usual. + */ + protected boolean onKeyEvent(KeyEvent event) { + return false; + } + + /** * Gets the root node in the currently active window if this service * can retrieve window content. * @@ -535,6 +563,11 @@ public abstract class AccessibilityService extends Service { public boolean onGesture(int gestureId) { return AccessibilityService.this.onGesture(gestureId); } + + @Override + public boolean onKeyEvent(KeyEvent event) { + return AccessibilityService.this.onKeyEvent(event); + } }); } @@ -554,11 +587,14 @@ public abstract class AccessibilityService extends Service { private static final int DO_ON_ACCESSIBILITY_EVENT = 30; private static final int DO_ON_GESTURE = 40; private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50; + private static final int DO_ON_KEY_EVENT = 60; private final HandlerCaller mCaller; private final Callbacks mCallback; + private int mConnectionId; + public IAccessibilityServiceClientWrapper(Context context, Looper looper, Callbacks callback) { mCallback = callback; @@ -591,41 +627,65 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } + @Override + public void onKeyEvent(KeyEvent event, int sequence) { + Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event); + mCaller.sendMessage(message); + } + public void executeMessage(Message message) { switch (message.what) { - case DO_ON_ACCESSIBILITY_EVENT : + case DO_ON_ACCESSIBILITY_EVENT: { AccessibilityEvent event = (AccessibilityEvent) message.obj; if (event != null) { AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); mCallback.onAccessibilityEvent(event); event.recycle(); } - return; - case DO_ON_INTERRUPT : + } return; + case DO_ON_INTERRUPT: { mCallback.onInterrupt(); - return; - case DO_SET_SET_CONNECTION : - final int connectionId = message.arg1; + } return; + case DO_SET_SET_CONNECTION: { + mConnectionId = message.arg1; IAccessibilityServiceConnection connection = (IAccessibilityServiceConnection) message.obj; if (connection != null) { - AccessibilityInteractionClient.getInstance().addConnection(connectionId, + AccessibilityInteractionClient.getInstance().addConnection(mConnectionId, connection); - mCallback.onSetConnectionId(connectionId); + mCallback.onSetConnectionId(mConnectionId); mCallback.onServiceConnected(); } else { - AccessibilityInteractionClient.getInstance().removeConnection(connectionId); + AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId); AccessibilityInteractionClient.getInstance().clearCache(); mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID); } - return; - case DO_ON_GESTURE : + } return; + case DO_ON_GESTURE: { final int gestureId = message.arg1; mCallback.onGesture(gestureId); - return; - case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: + } return; + case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { AccessibilityInteractionClient.getInstance().clearCache(); - return; + } return; + case DO_ON_KEY_EVENT: { + KeyEvent event = (KeyEvent) message.obj; + try { + IAccessibilityServiceConnection connection = AccessibilityInteractionClient + .getInstance().getConnection(mConnectionId); + if (connection != null) { + final boolean result = mCallback.onKeyEvent(event); + final int sequence = message.arg1; + try { + connection.setOnKeyEventResult(result, sequence); + } catch (RemoteException re) { + /* ignore */ + } + } + } finally { + event.recycle(); + } + } return; default : Log.w(LOG_TAG, "Unknown message type " + message.what); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 5d684e358f56..c5e3d43a527a 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -18,6 +18,7 @@ package android.accessibilityservice; import android.accessibilityservice.IAccessibilityServiceConnection; import android.view.accessibility.AccessibilityEvent; +import android.view.KeyEvent; /** * Top-level interface to an accessibility service component. @@ -35,4 +36,6 @@ import android.view.accessibility.AccessibilityEvent; void onGesture(int gesture); void clearAccessibilityNodeInfoCache(); + + void onKeyEvent(in KeyEvent event, int sequence); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 7a29f359678b..3df06b505675 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -31,144 +31,31 @@ interface IAccessibilityServiceConnection { void setServiceInfo(in AccessibilityServiceInfo info); - /** - * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param flags Additional flags. - * @param threadId The id of the calling thread. - * @return Whether the call succeeded. - */ boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId); - /** - * Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text. - * The match is case insensitive containment. The search is performed in the window - * whose id is specified and starts from the node whose accessibility id is specified. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param text The searched text. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return Whether the call succeeded. - */ boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - /** - * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search - * is performed in the window whose id is specified and starts from the node whose - * accessibility id is specified. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param viewId The fully qualified resource name of the view id to find. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return Whether the call succeeded. - */ boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - /** - * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified - * focus type. The search is performed in the window whose id is specified and starts from - * the node whose accessibility id is specified. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param focusType The type of focus to find. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return Whether the call succeeded. - */ boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - /** - * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} to take accessibility - * focus in the given direction. The search is performed in the window whose id is - * specified and starts from the node whose accessibility id is specified. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param direction The direction in which to search for focusable. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return Whether the call succeeded. - */ boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - /** - * Performs an accessibility action on an - * {@link android.view.accessibility.AccessibilityNodeInfo}. - * - * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} - * to query the currently active window. - * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} - * to start from the root. - * @param action The action to perform. - * @param arguments Optional action arguments. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return Whether the action was performed. - */ boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); - /** - * @return The associated accessibility service info. - */ AccessibilityServiceInfo getServiceInfo(); - /** - * Performs a global action, such as going home, going back, etc. - * - * @param action The action to perform. - * @return Whether the action was performed. - */ boolean performGlobalAction(int action); + + oneway void setOnKeyEventResult(boolean handled, int sequence); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 7d02342ca59c..d9799b652919 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -31,6 +31,7 @@ import android.os.SystemClock; import android.util.Log; import android.view.Display; import android.view.InputEvent; +import android.view.KeyEvent; import android.view.Surface; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; @@ -693,6 +694,11 @@ public final class UiAutomation { listener.onAccessibilityEvent(AccessibilityEvent.obtain(event)); } } + + @Override + public boolean onKeyEvent(KeyEvent event) { + return false; + } }); } } diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java index 179db1235b21..0d8a571de721 100644 --- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -25,6 +25,7 @@ import android.view.Display; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputFilter; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; @@ -80,7 +81,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final Choreographer mChoreographer; - private int mCurrentDeviceId; + private int mCurrentTouchDeviceId; private boolean mInstalled; @@ -98,6 +99,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private boolean mHoverEventSequenceStarted; + private boolean mKeyEventSequenceStarted; + AccessibilityInputFilter(Context context, AccessibilityManagerService service) { super(context.getMainLooper()); mContext = context; @@ -133,11 +136,21 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); } - if (mEventHandler == null) { + if (event instanceof MotionEvent + && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { + MotionEvent motionEvent = (MotionEvent) event; + onMotionEvent(motionEvent, policyFlags); + } else if (event instanceof KeyEvent + && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { + KeyEvent keyEvent = (KeyEvent) event; + onKeyEvent(keyEvent, policyFlags); + } else { super.onInputEvent(event, policyFlags); - return; } - if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) { + } + + private void onMotionEvent(MotionEvent event, int policyFlags) { + if (mEventHandler == null) { super.onInputEvent(event, policyFlags); return; } @@ -149,26 +162,25 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return; } final int deviceId = event.getDeviceId(); - if (mCurrentDeviceId != deviceId) { + if (mCurrentTouchDeviceId != deviceId) { + mCurrentTouchDeviceId = deviceId; mMotionEventSequenceStarted = false; mHoverEventSequenceStarted = false; mEventHandler.clear(); - mCurrentDeviceId = deviceId; } - if (mCurrentDeviceId < 0) { + if (mCurrentTouchDeviceId < 0) { super.onInputEvent(event, policyFlags); return; } // We do not handle scroll events. - MotionEvent motionEvent = (MotionEvent) event; - if (motionEvent.getActionMasked() == MotionEvent.ACTION_SCROLL) { + if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { super.onInputEvent(event, policyFlags); return; } // Wait for a down touch event to start processing. - if (motionEvent.isTouchEvent()) { + if (event.isTouchEvent()) { if (!mMotionEventSequenceStarted) { - if (motionEvent.getActionMasked() != MotionEvent.ACTION_DOWN) { + if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { return; } mMotionEventSequenceStarted = true; @@ -176,7 +188,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } else { // Wait for an enter hover event to start processing. if (!mHoverEventSequenceStarted) { - if (motionEvent.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) { + if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) { return; } mHoverEventSequenceStarted = true; @@ -185,6 +197,22 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo batchMotionEvent((MotionEvent) event, policyFlags); } + private void onKeyEvent(KeyEvent event, int policyFlags) { + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { + mKeyEventSequenceStarted = false; + super.onInputEvent(event, policyFlags); + return; + } + // Wait for a down key event to start processing. + if (!mKeyEventSequenceStarted) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return; + } + mKeyEventSequenceStarted = true; + } + mAms.notifyKeyEvent(event, policyFlags); + } + private void scheduleProcessBatchedEvents() { mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mProcessBatchedEventsRunnable, null); @@ -286,6 +314,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + void reset() { + setEnabledFeatures(0); + mKeyEventSequenceStarted = false; + mMotionEventSequenceStarted = false; + mHoverEventSequenceStarted = false; + } + private void enableFeatures() { mMotionEventSequenceStarted = false; mHoverEventSequenceStarted = false; diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 527e891ff731..110c4dadc201 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -61,16 +61,20 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; +import android.util.Pools.Pool; +import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.IWindow; import android.view.IWindowManager; import android.view.InputDevice; +import android.view.InputEventConsistencyVerifier; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; @@ -132,6 +136,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private static final int OWN_PROCESS_ID = android.os.Process.myPid(); + private static final int MAX_POOL_SIZE = 10; + private static int sIdCounter = 0; private static int sNextWindowId; @@ -140,6 +146,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Object mLock = new Object(); + private final Pool<PendingEvent> mPendingEventPool = + new SimplePool<PendingEvent>(MAX_POOL_SIZE); + private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); @@ -633,6 +642,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + boolean notifyKeyEvent(KeyEvent event, int policyFlags) { + synchronized (mLock) { + KeyEvent localClone = KeyEvent.obtain(event); + boolean handled = notifyKeyEventLocked(localClone, policyFlags, false); + if (!handled) { + handled = notifyKeyEventLocked(localClone, policyFlags, true); + } + return handled; + } + } + /** * Gets the bounds of the accessibility focus in the active window. * @@ -798,6 +818,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } + private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) { + // TODO: Now we are giving the key events to the last enabled + // service that can handle them which is the last one + // in our list since we write the last enabled as the + // last record in the enabled services setting. Ideally, + // the user should make the call which service handles + // key events. However, only one service should handle + // key events to avoid user frustration when different + // behavior is observed from different combinations of + // enabled accessibility services. + UserState state = getCurrentUserStateLocked(); + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + Service service = state.mBoundServices.get(i); + if (service.mIsDefault == isDefault) { + service.notifyKeyEvent(event, policyFlags); + return true; + } + } + return false; + } + private void notifyClearAccessibilityNodeInfoCacheLocked() { UserState state = getCurrentUserStateLocked(); for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { @@ -1119,8 +1160,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { boolean setInputFilter = false; AccessibilityInputFilter inputFilter = null; synchronized (mLock) { - if ((userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) - || userState.mIsDisplayMagnificationEnabled) { + if (userState.mIsAccessibilityEnabled) { if (!mHasInputFilter) { mHasInputFilter = true; if (mInputFilter == null) { @@ -1141,7 +1181,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } else { if (mHasInputFilter) { mHasInputFilter = false; - mInputFilter.setEnabledFeatures(0); + mInputFilter.reset(); inputFilter = null; setInputFilter = true; } @@ -1446,6 +1486,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5; public static final int MSG_UPDATE_INPUT_FILTER = 6; public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7; + public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8; public MainHandler(Looper looper) { super(looper); @@ -1464,6 +1505,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } event.recycle(); } break; + case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: { + KeyEvent event = (KeyEvent) msg.obj; + final int policyFlags = msg.arg1; + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.sendInputEvent(event, policyFlags); + } + } + event.recycle(); + } break; case MSG_SEND_STATE_TO_CLIENTS: { final int clientState = msg.arg1; final int userId = msg.arg2; @@ -1536,6 +1587,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) { + PendingEvent pendingEvent = mPendingEventPool.acquire(); + if (pendingEvent == null) { + pendingEvent = new PendingEvent(); + } + pendingEvent.event = event; + pendingEvent.policyFlags = policyFlags; + pendingEvent.sequence = sequence; + return pendingEvent; + } + + private void recyclePendingEventLocked(PendingEvent pendingEvent) { + pendingEvent.clear(); + mPendingEventPool.release(pendingEvent); + } + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the @@ -1545,12 +1612,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * connection for the service. */ class Service extends IAccessibilityServiceConnection.Stub - implements ServiceConnection, DeathRecipient { - - // We pick the MSBs to avoid collision since accessibility event types are - // used as message types allowing us to remove messages per event type. - private static final int MSG_ON_GESTURE = 0x80000000; - private static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 0x40000000; + implements ServiceConnection, DeathRecipient {; final int mUserId; @@ -1594,29 +1656,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<AccessibilityEvent>(); - /** - * Handler for delayed event dispatch. - */ - public Handler mHandler = new Handler(mMainHandler.getLooper()) { + final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher(); + + // Handler only for dispatching accessibility events since we use event + // types as message types allowing us to remove messages per event type. + public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) { @Override public void handleMessage(Message message) { - final int type = message.what; - switch (type) { - case MSG_ON_GESTURE: { - final int gestureId = message.arg1; - notifyGestureInternal(gestureId); - } break; - case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { - notifyClearAccessibilityNodeInfoCacheInternal(); - } break; - default: { - final int eventType = type; - notifyAccessibilityEventInternal(eventType); - } break; - } + final int eventType = message.what; + notifyAccessibilityEventInternal(eventType); } }; + // Handler for scheduling method invocations on the main thread. + public InvocationHandler mInvocationHandler = new InvocationHandler( + mMainHandler.getLooper()); + public Service(int userId, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo) { mUserId = userId; @@ -1703,6 +1758,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } UserState userState = getUserStateLocked(mUserId); + mKeyEventDispatcher.flush(); if (!mIsAutomation) { mContext.unbindService(this); } else { @@ -1718,6 +1774,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override + public void setOnKeyEventResult(boolean handled, int sequence) { + mKeyEventDispatcher.setOnKeyEventResult(handled, sequence); + } + + @Override public AccessibilityServiceInfo getServiceInfo() { synchronized (mLock) { return mAccessibilityServiceInfo; @@ -2109,6 +2170,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void binderDied() { synchronized (mLock) { + mKeyEventDispatcher.flush(); UserState userState = getUserStateLocked(mUserId); // The death recipient is unregistered in removeServiceLocked removeServiceLocked(this, userState); @@ -2141,12 +2203,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int what = eventType; if (oldEvent != null) { - mHandler.removeMessages(what); + mEventDispatchHandler.removeMessages(what); oldEvent.recycle(); } - Message message = mHandler.obtainMessage(what); - mHandler.sendMessageDelayed(message, mNotificationTimeout); + Message message = mEventDispatchHandler.obtainMessage(what); + mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout); } } @@ -2211,11 +2273,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } public void notifyGesture(int gestureId) { - mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget(); + mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE, + gestureId, 0).sendToTarget(); + } + + public void notifyKeyEvent(KeyEvent event, int policyFlags) { + mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT, + policyFlags, 0, event).sendToTarget(); } public void notifyClearAccessibilityNodeInfoCache() { - mHandler.sendEmptyMessage(MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); + mInvocationHandler.sendEmptyMessage( + InvocationHandler.MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); } private void notifyGestureInternal(int gestureId) { @@ -2230,6 +2299,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void notifyKeyEventInternal(KeyEvent event, int policyFlags) { + mKeyEventDispatcher.notifyKeyEvent(event, policyFlags); + } + private void notifyClearAccessibilityNodeInfoCacheInternal() { IAccessibilityServiceClient listener = mServiceInterface; if (listener != null) { @@ -2339,6 +2412,177 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } return null; } + + private final class InvocationHandler extends Handler { + + public static final int MSG_ON_GESTURE = 1; + public static final int MSG_ON_KEY_EVENT = 2; + public static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 3; + public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4; + + public InvocationHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message message) { + final int type = message.what; + switch (type) { + case MSG_ON_GESTURE: { + final int gestureId = message.arg1; + notifyGestureInternal(gestureId); + } break; + case MSG_ON_KEY_EVENT: { + KeyEvent event = (KeyEvent) message.obj; + final int policyFlags = message.arg1; + notifyKeyEventInternal(event, policyFlags); + } break; + case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { + notifyClearAccessibilityNodeInfoCacheInternal(); + } break; + case MSG_ON_KEY_EVENT_TIMEOUT: { + PendingEvent eventState = (PendingEvent) message.obj; + setOnKeyEventResult(false, eventState.sequence); + } break; + default: { + throw new IllegalArgumentException("Unknown message: " + type); + } + } + } + } + + private final class KeyEventDispatcher { + + private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500; + + private PendingEvent mPendingEvents; + + private final InputEventConsistencyVerifier mSentEventsVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() + ? new InputEventConsistencyVerifier( + this, 0, KeyEventDispatcher.class.getSimpleName()) : null; + + public void notifyKeyEvent(KeyEvent event, int policyFlags) { + final PendingEvent pendingEvent; + + synchronized (mLock) { + pendingEvent = addPendingEventLocked(event, policyFlags); + } + + Message message = mInvocationHandler.obtainMessage( + InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent); + mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS); + + try { + // Accessibility services are exclusively not in the system + // process, therefore no need to clone the motion event to + // prevent tampering. It will be cloned in the IPC call. + mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence); + } catch (RemoteException re) { + setOnKeyEventResult(false, pendingEvent.sequence); + } + } + + public void setOnKeyEventResult(boolean handled, int sequence) { + synchronized (mLock) { + PendingEvent pendingEvent = removePendingEventLocked(sequence); + if (pendingEvent != null) { + mInvocationHandler.removeMessages( + InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, + pendingEvent); + pendingEvent.handled = handled; + finishPendingEventLocked(pendingEvent); + } + } + } + + public void flush() { + synchronized (mLock) { + cancelAllPendingEventsLocked(); + mSentEventsVerifier.reset(); + } + } + + private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) { + final int sequence = event.getSequenceNumber(); + PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence); + pendingEvent.next = mPendingEvents; + mPendingEvents = pendingEvent; + return pendingEvent; + } + + private PendingEvent removePendingEventLocked(int sequence) { + PendingEvent previous = null; + PendingEvent current = mPendingEvents; + + while (current != null) { + if (current.sequence == sequence) { + if (previous != null) { + previous.next = current.next; + } else { + mPendingEvents = current.next; + } + current.next = null; + return current; + } + previous = current; + current = current.next; + } + return null; + } + + private void finishPendingEventLocked(PendingEvent pendingEvent) { + if (!pendingEvent.handled) { + sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags); + } + // Nullify the event since we do not want it to be + // recycled yet. It will be sent to the input filter. + pendingEvent.event = null; + recyclePendingEventLocked(pendingEvent); + } + + private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) { + if (DEBUG) { + Slog.i(LOG_TAG, "Injecting event: " + event); + } + if (mSentEventsVerifier != null) { + mSentEventsVerifier.onKeyEvent(event, 0); + } + policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; + mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, + policyFlags, 0, event).sendToTarget(); + } + + private void cancelAllPendingEventsLocked() { + while (mPendingEvents != null) { + PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence); + pendingEvent.handled = false; + mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, + pendingEvent); + finishPendingEventLocked(pendingEvent); + } + } + } + } + + private static final class PendingEvent { + PendingEvent next; + + KeyEvent event; + int policyFlags; + int sequence; + boolean handled; + + public void clear() { + if (event != null) { + event.recycle(); + event = null; + } + next = null; + policyFlags = 0; + sequence = 0; + handled = false; + } } final class SecurityPolicy { diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java index 3289a1592199..8c93e7b4440d 100644 --- a/services/java/com/android/server/accessibility/EventStreamTransformation.java +++ b/services/java/com/android/server/accessibility/EventStreamTransformation.java @@ -57,7 +57,7 @@ import android.view.accessibility.AccessibilityEvent; interface EventStreamTransformation { /** - * Receives motion event. Passed are the event transformed by previous + * Receives a motion event. Passed are the event transformed by previous * transformations and the raw event to which no transformations have * been applied. * |