Merge "Revert "Dispatch a11y events in separate thread."" into nyc-mr1-dev
This commit is contained in:
@@ -21,7 +21,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
|
|||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ParceledListSlice;
|
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -92,9 +91,6 @@ public final class AccessibilityManager {
|
|||||||
/** @hide */
|
/** @hide */
|
||||||
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
|
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
|
||||||
|
|
||||||
/** @hide */
|
|
||||||
public static final int MAX_A11Y_EVENTS_PER_SERVICE_CALL = 20;
|
|
||||||
|
|
||||||
static final Object sInstanceSync = new Object();
|
static final Object sInstanceSync = new Object();
|
||||||
|
|
||||||
private static AccessibilityManager sInstance;
|
private static AccessibilityManager sInstance;
|
||||||
@@ -103,8 +99,6 @@ public final class AccessibilityManager {
|
|||||||
|
|
||||||
private IAccessibilityManager mService;
|
private IAccessibilityManager mService;
|
||||||
|
|
||||||
private EventDispatchThread mEventDispatchThread;
|
|
||||||
|
|
||||||
final int mUserId;
|
final int mUserId;
|
||||||
|
|
||||||
final Handler mHandler;
|
final Handler mHandler;
|
||||||
@@ -176,7 +170,7 @@ public final class AccessibilityManager {
|
|||||||
private final IAccessibilityManagerClient.Stub mClient =
|
private final IAccessibilityManagerClient.Stub mClient =
|
||||||
new IAccessibilityManagerClient.Stub() {
|
new IAccessibilityManagerClient.Stub() {
|
||||||
public void setState(int state) {
|
public void setState(int state) {
|
||||||
// We do not want to change this immediately as the application may
|
// We do not want to change this immediately as the applicatoin may
|
||||||
// have already checked that accessibility is on and fired an event,
|
// have already checked that accessibility is on and fired an event,
|
||||||
// that is now propagating up the view tree, Hence, if accessibility
|
// that is now propagating up the view tree, Hence, if accessibility
|
||||||
// is now off an exception will be thrown. We want to have the exception
|
// is now off an exception will be thrown. We want to have the exception
|
||||||
@@ -303,32 +297,47 @@ public final class AccessibilityManager {
|
|||||||
* their descendants.
|
* their descendants.
|
||||||
*/
|
*/
|
||||||
public void sendAccessibilityEvent(AccessibilityEvent event) {
|
public void sendAccessibilityEvent(AccessibilityEvent event) {
|
||||||
if (!isEnabled()) {
|
final IAccessibilityManager service;
|
||||||
Looper myLooper = Looper.myLooper();
|
final int userId;
|
||||||
if (myLooper == Looper.getMainLooper()) {
|
synchronized (mLock) {
|
||||||
throw new IllegalStateException(
|
service = getServiceLocked();
|
||||||
"Accessibility off. Did you forget to check that?");
|
if (service == null) {
|
||||||
} else {
|
|
||||||
// If we're not running on the thread with the main looper, it's possible for
|
|
||||||
// the state of accessibility to change between checking isEnabled and
|
|
||||||
// calling this method. So just log the error rather than throwing the
|
|
||||||
// exception.
|
|
||||||
Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
if (!mIsEnabled) {
|
||||||
event.setEventTime(SystemClock.uptimeMillis());
|
Looper myLooper = Looper.myLooper();
|
||||||
|
if (myLooper == Looper.getMainLooper()) {
|
||||||
getEventDispatchThread().scheduleEvent(event);
|
throw new IllegalStateException(
|
||||||
}
|
"Accessibility off. Did you forget to check that?");
|
||||||
|
} else {
|
||||||
private EventDispatchThread getEventDispatchThread() {
|
// If we're not running on the thread with the main looper, it's possible for
|
||||||
synchronized (mLock) {
|
// the state of accessibility to change between checking isEnabled and
|
||||||
if (mEventDispatchThread == null) {
|
// calling this method. So just log the error rather than throwing the
|
||||||
mEventDispatchThread = new EventDispatchThread(mService, mUserId);
|
// exception.
|
||||||
mEventDispatchThread.start();
|
Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userId = mUserId;
|
||||||
|
}
|
||||||
|
boolean doRecycle = false;
|
||||||
|
try {
|
||||||
|
event.setEventTime(SystemClock.uptimeMillis());
|
||||||
|
// it is possible that this manager is in the same process as the service but
|
||||||
|
// client using it is called through Binder from another process. Example: MMS
|
||||||
|
// app adds a SMS notification and the NotificationManagerService calls this method
|
||||||
|
long identityToken = Binder.clearCallingIdentity();
|
||||||
|
doRecycle = service.sendAccessibilityEvent(event, userId);
|
||||||
|
Binder.restoreCallingIdentity(identityToken);
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(LOG_TAG, event + " sent");
|
||||||
|
}
|
||||||
|
} catch (RemoteException re) {
|
||||||
|
Log.e(LOG_TAG, "Error during sending " + event + " ", re);
|
||||||
|
} finally {
|
||||||
|
if (doRecycle) {
|
||||||
|
event.recycle();
|
||||||
}
|
}
|
||||||
return mEventDispatchThread;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +620,7 @@ public final class AccessibilityManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IAccessibilityManager getServiceLocked() {
|
private IAccessibilityManager getServiceLocked() {
|
||||||
if (mService == null) {
|
if (mService == null) {
|
||||||
tryConnectToServiceLocked(null);
|
tryConnectToServiceLocked(null);
|
||||||
}
|
}
|
||||||
@@ -713,99 +722,4 @@ public final class AccessibilityManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EventDispatchThread extends Thread {
|
|
||||||
// Second lock used to keep UI thread performant. Never try to grab mLock when holding
|
|
||||||
// this one, or the UI thread will block in send AccessibilityEvent.
|
|
||||||
private final Object mEventQueueLock = new Object();
|
|
||||||
|
|
||||||
// Two lists to hold events. The app thread fills one while we empty the other.
|
|
||||||
private final ArrayList<AccessibilityEvent> mEventLists0 =
|
|
||||||
new ArrayList<>(MAX_A11Y_EVENTS_PER_SERVICE_CALL);
|
|
||||||
private final ArrayList<AccessibilityEvent> mEventLists1 =
|
|
||||||
new ArrayList<>(MAX_A11Y_EVENTS_PER_SERVICE_CALL);
|
|
||||||
|
|
||||||
private boolean mPingPongListToggle;
|
|
||||||
|
|
||||||
private final IAccessibilityManager mService;
|
|
||||||
|
|
||||||
private final int mUserId;
|
|
||||||
|
|
||||||
EventDispatchThread(IAccessibilityManager service, int userId) {
|
|
||||||
mService = service;
|
|
||||||
mUserId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (true) {
|
|
||||||
ArrayList<AccessibilityEvent> listBeingDrained;
|
|
||||||
synchronized (mEventQueueLock) {
|
|
||||||
ArrayList<AccessibilityEvent> listBeingFilled = getListBeingFilledLocked();
|
|
||||||
if (listBeingFilled.isEmpty()) {
|
|
||||||
try {
|
|
||||||
mEventQueueLock.wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Treat as a notify
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Swap buffers
|
|
||||||
mPingPongListToggle = !mPingPongListToggle;
|
|
||||||
listBeingDrained = listBeingFilled;
|
|
||||||
}
|
|
||||||
dispatchEvents(listBeingDrained);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void scheduleEvent(AccessibilityEvent event) {
|
|
||||||
synchronized (mEventQueueLock) {
|
|
||||||
getListBeingFilledLocked().add(event);
|
|
||||||
mEventQueueLock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayList<AccessibilityEvent> getListBeingFilledLocked() {
|
|
||||||
return (mPingPongListToggle) ? mEventLists0 : mEventLists1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchEvents(ArrayList<AccessibilityEvent> events) {
|
|
||||||
int eventListCapacityLowerBound = events.size();
|
|
||||||
while (events.size() > 0) {
|
|
||||||
// We don't want to consume extra memory if an app sends a lot of events in a
|
|
||||||
// one-off event. Cap the list length at double the max events per call.
|
|
||||||
// We'll end up with extra GC for apps that send huge numbers of events, but
|
|
||||||
// sending that many events will lead to bad performance in any case.
|
|
||||||
if ((eventListCapacityLowerBound > 2 * MAX_A11Y_EVENTS_PER_SERVICE_CALL)
|
|
||||||
&& (events.size() <= 2 * MAX_A11Y_EVENTS_PER_SERVICE_CALL)) {
|
|
||||||
events.trimToSize();
|
|
||||||
eventListCapacityLowerBound = events.size();
|
|
||||||
}
|
|
||||||
// We only expect this loop to run once, as the app shouldn't be sending
|
|
||||||
// huge numbers of events.
|
|
||||||
// The clear in the called method will remove the sent events
|
|
||||||
dispatchOneBatchOfEvents(events.subList(0,
|
|
||||||
Math.min(events.size(), MAX_A11Y_EVENTS_PER_SERVICE_CALL)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchOneBatchOfEvents(List<AccessibilityEvent> events) {
|
|
||||||
if (events.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long identityToken = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
mService.sendAccessibilityEvents(new ParceledListSlice<>(events),
|
|
||||||
mUserId);
|
|
||||||
} catch (RemoteException re) {
|
|
||||||
Log.e(LOG_TAG, "Error sending multiple events");
|
|
||||||
}
|
|
||||||
Binder.restoreCallingIdentity(identityToken);
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.i(LOG_TAG, events.size() + " events sent");
|
|
||||||
}
|
|
||||||
for (int i = events.size() - 1; i >= 0; i--) {
|
|
||||||
events.remove(i).recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
|
|||||||
import android.accessibilityservice.IAccessibilityServiceConnection;
|
import android.accessibilityservice.IAccessibilityServiceConnection;
|
||||||
import android.accessibilityservice.IAccessibilityServiceClient;
|
import android.accessibilityservice.IAccessibilityServiceClient;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.pm.ParceledListSlice;
|
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.accessibility.IAccessibilityInteractionConnection;
|
import android.view.accessibility.IAccessibilityInteractionConnection;
|
||||||
@@ -38,9 +37,7 @@ interface IAccessibilityManager {
|
|||||||
|
|
||||||
int addClient(IAccessibilityManagerClient client, int userId);
|
int addClient(IAccessibilityManagerClient client, int userId);
|
||||||
|
|
||||||
void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
|
boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
|
||||||
|
|
||||||
void sendAccessibilityEvents(in ParceledListSlice events, int userId);
|
|
||||||
|
|
||||||
List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
|
List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
|
||||||
|
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
|
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
// We treat calls from a profile as if made by its parent as profiles
|
// We treat calls from a profile as if made by its parent as profiles
|
||||||
// share the accessibility state of the parent. The call below
|
// share the accessibility state of the parent. The call below
|
||||||
@@ -459,39 +459,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
|
|||||||
final int resolvedUserId = mSecurityPolicy
|
final int resolvedUserId = mSecurityPolicy
|
||||||
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
|
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
|
||||||
// This method does nothing for a background user.
|
// This method does nothing for a background user.
|
||||||
if (resolvedUserId == mCurrentUserId) {
|
if (resolvedUserId != mCurrentUserId) {
|
||||||
if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
|
return true; // yes, recycle the event
|
||||||
mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(
|
}
|
||||||
event.getWindowId(), event.getSourceNodeId(),
|
if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
|
||||||
event.getEventType(), event.getAction());
|
mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(event.getWindowId(),
|
||||||
mSecurityPolicy.updateEventSourceLocked(event);
|
event.getSourceNodeId(), event.getEventType(), event.getAction());
|
||||||
notifyAccessibilityServicesDelayedLocked(event, false);
|
mSecurityPolicy.updateEventSourceLocked(event);
|
||||||
notifyAccessibilityServicesDelayedLocked(event, true);
|
notifyAccessibilityServicesDelayedLocked(event, false);
|
||||||
}
|
notifyAccessibilityServicesDelayedLocked(event, true);
|
||||||
if (mHasInputFilter && mInputFilter != null) {
|
}
|
||||||
mMainHandler.obtainMessage(
|
if (mHasInputFilter && mInputFilter != null) {
|
||||||
MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER,
|
mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER,
|
||||||
AccessibilityEvent.obtain(event)).sendToTarget();
|
AccessibilityEvent.obtain(event)).sendToTarget();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (OWN_PROCESS_ID != Binder.getCallingPid()) {
|
|
||||||
event.recycle();
|
event.recycle();
|
||||||
}
|
}
|
||||||
}
|
return (OWN_PROCESS_ID != Binder.getCallingPid());
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendAccessibilityEvents(ParceledListSlice events, int userId) {
|
|
||||||
List<AccessibilityEvent> a11yEvents = events.getList();
|
|
||||||
// Grab the lock once for the entire batch
|
|
||||||
synchronized (mLock) {
|
|
||||||
int numEventsToProcess = Math.min(a11yEvents.size(),
|
|
||||||
AccessibilityManager.MAX_A11Y_EVENTS_PER_SERVICE_CALL);
|
|
||||||
for (int i = 0; i < numEventsToProcess; i++) {
|
|
||||||
AccessibilityEvent event = a11yEvents.get(i);
|
|
||||||
sendAccessibilityEvent(event, userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -131,10 +131,18 @@ public class AccessibilityManagerTest extends AndroidTestCase {
|
|||||||
public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
|
public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
|
||||||
AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
|
AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
|
||||||
|
|
||||||
|
when(mMockService.sendAccessibilityEvent(eq(sentEvent), anyInt()))
|
||||||
|
.thenReturn(true /* should recycle event object */)
|
||||||
|
.thenReturn(false /* should not recycle event object */);
|
||||||
|
|
||||||
AccessibilityManager manager = createManager(true);
|
AccessibilityManager manager = createManager(true);
|
||||||
manager.sendAccessibilityEvent(sentEvent);
|
manager.sendAccessibilityEvent(sentEvent);
|
||||||
|
|
||||||
assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
|
assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
|
||||||
|
|
||||||
|
manager.sendAccessibilityEvent(sentEvent);
|
||||||
|
|
||||||
|
assertNotSame("The event should not be recycled.", sentEvent, AccessibilityEvent.obtain());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|||||||
Reference in New Issue
Block a user