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:
Svetoslav
2013-04-11 22:48:39 -07:00
committed by Android Git Automerger
8 changed files with 410 additions and 174 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
});
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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.
*