diff --git a/api/current.txt b/api/current.txt index 1e1f01ca5c11c..58391afb7c861 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 811b92a5e102c..31de98dc1593f 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; @@ -412,6 +414,32 @@ public abstract class AccessibilityService extends Service { return false; } + /** + * 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. + *
+ * Note: 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. + *
+ *+ * Note: 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. + *
+ *
+ * @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 5d684e358f562..c5e3d43a527ac 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 7a29f359678b8..3df06b505675a 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 7d02342ca59cc..d9799b6529191 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 179db1235b21e..0d8a571de7211 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 527e891ff7319..110c4dadc201c 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