am 14c8c741: Merge "Adding APIs for an accessibility service to intercept key events." into jb-mr2-dev
* commit '14c8c741f79983578a8e9c5124d142c6d85ab91b': Adding APIs for an accessibility service to intercept key events.
This commit is contained in:
@@ -2075,6 +2075,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);
|
||||
|
||||
@@ -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.
|
||||
* <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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -1717,6 +1773,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
|
||||
return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnKeyEventResult(boolean handled, int sequence) {
|
||||
mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityServiceInfo getServiceInfo() {
|
||||
synchronized (mLock) {
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user