Loading core/java/android/view/accessibility/AccessibilityManager.java +125 −39 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Handler; Loading Loading @@ -91,6 +92,9 @@ public final class AccessibilityManager { /** @hide */ 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(); private static AccessibilityManager sInstance; Loading @@ -99,6 +103,8 @@ public final class AccessibilityManager { private IAccessibilityManager mService; private EventDispatchThread mEventDispatchThread; final int mUserId; final Handler mHandler; Loading Loading @@ -170,7 +176,7 @@ public final class AccessibilityManager { private final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { public void setState(int state) { // We do not want to change this immediately as the applicatoin may // We do not want to change this immediately as the application may // have already checked that accessibility is on and fired an event, // 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 Loading Loading @@ -297,14 +303,7 @@ public final class AccessibilityManager { * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { final IAccessibilityManager service; final int userId; synchronized (mLock) { service = getServiceLocked(); if (service == null) { return; } if (!mIsEnabled) { if (!isEnabled()) { Looper myLooper = Looper.myLooper(); if (myLooper == Looper.getMainLooper()) { throw new IllegalStateException( Loading @@ -318,26 +317,18 @@ public final class AccessibilityManager { 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"); getEventDispatchThread().scheduleEvent(event); } } catch (RemoteException re) { Log.e(LOG_TAG, "Error during sending " + event + " ", re); } finally { if (doRecycle) { event.recycle(); private EventDispatchThread getEventDispatchThread() { synchronized (mLock) { if (mEventDispatchThread == null) { mEventDispatchThread = new EventDispatchThread(mService, mUserId); mEventDispatchThread.start(); } return mEventDispatchThread; } } Loading Loading @@ -722,4 +713,99 @@ 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(); } } } } core/java/android/view/accessibility/IAccessibilityManager.aidl +4 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; Loading @@ -37,7 +38,9 @@ interface IAccessibilityManager { int addClient(IAccessibilityManagerClient client, int userId); boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); void sendAccessibilityEvents(in ParceledListSlice events, int userId); List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId); Loading services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +31 −15 Original line number Diff line number Diff line Loading @@ -451,7 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) { public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below Loading @@ -459,23 +459,39 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); // This method does nothing for a background user. if (resolvedUserId != mCurrentUserId) { return true; // yes, recycle the event } if (resolvedUserId == mCurrentUserId) { if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction()); mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked( event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction()); mSecurityPolicy.updateEventSourceLocked(event); notifyAccessibilityServicesDelayedLocked(event, false); notifyAccessibilityServicesDelayedLocked(event, true); } if (mHasInputFilter && mInputFilter != null) { mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, mMainHandler.obtainMessage( MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, AccessibilityEvent.obtain(event)).sendToTarget(); } } } if (OWN_PROCESS_ID != Binder.getCallingPid()) { 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 Loading services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +0 −8 Original line number Diff line number Diff line Loading @@ -131,18 +131,10 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { 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); manager.sendAccessibilityEvent(sentEvent); assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain()); manager.sendAccessibilityEvent(sentEvent); assertNotSame("The event should not be recycled.", sentEvent, AccessibilityEvent.obtain()); } @MediumTest Loading Loading
core/java/android/view/accessibility/AccessibilityManager.java +125 −39 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Handler; Loading Loading @@ -91,6 +92,9 @@ public final class AccessibilityManager { /** @hide */ 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(); private static AccessibilityManager sInstance; Loading @@ -99,6 +103,8 @@ public final class AccessibilityManager { private IAccessibilityManager mService; private EventDispatchThread mEventDispatchThread; final int mUserId; final Handler mHandler; Loading Loading @@ -170,7 +176,7 @@ public final class AccessibilityManager { private final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { public void setState(int state) { // We do not want to change this immediately as the applicatoin may // We do not want to change this immediately as the application may // have already checked that accessibility is on and fired an event, // 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 Loading Loading @@ -297,14 +303,7 @@ public final class AccessibilityManager { * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { final IAccessibilityManager service; final int userId; synchronized (mLock) { service = getServiceLocked(); if (service == null) { return; } if (!mIsEnabled) { if (!isEnabled()) { Looper myLooper = Looper.myLooper(); if (myLooper == Looper.getMainLooper()) { throw new IllegalStateException( Loading @@ -318,26 +317,18 @@ public final class AccessibilityManager { 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"); getEventDispatchThread().scheduleEvent(event); } } catch (RemoteException re) { Log.e(LOG_TAG, "Error during sending " + event + " ", re); } finally { if (doRecycle) { event.recycle(); private EventDispatchThread getEventDispatchThread() { synchronized (mLock) { if (mEventDispatchThread == null) { mEventDispatchThread = new EventDispatchThread(mService, mUserId); mEventDispatchThread.start(); } return mEventDispatchThread; } } Loading Loading @@ -722,4 +713,99 @@ 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(); } } } }
core/java/android/view/accessibility/IAccessibilityManager.aidl +4 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; Loading @@ -37,7 +38,9 @@ interface IAccessibilityManager { int addClient(IAccessibilityManagerClient client, int userId); boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); void sendAccessibilityEvents(in ParceledListSlice events, int userId); List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId); Loading
services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +31 −15 Original line number Diff line number Diff line Loading @@ -451,7 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) { public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below Loading @@ -459,23 +459,39 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); // This method does nothing for a background user. if (resolvedUserId != mCurrentUserId) { return true; // yes, recycle the event } if (resolvedUserId == mCurrentUserId) { if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction()); mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked( event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction()); mSecurityPolicy.updateEventSourceLocked(event); notifyAccessibilityServicesDelayedLocked(event, false); notifyAccessibilityServicesDelayedLocked(event, true); } if (mHasInputFilter && mInputFilter != null) { mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, mMainHandler.obtainMessage( MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, AccessibilityEvent.obtain(event)).sendToTarget(); } } } if (OWN_PROCESS_ID != Binder.getCallingPid()) { 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 Loading
services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +0 −8 Original line number Diff line number Diff line Loading @@ -131,18 +131,10 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { 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); manager.sendAccessibilityEvent(sentEvent); assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain()); manager.sendAccessibilityEvent(sentEvent); assertNotSame("The event should not be recycled.", sentEvent, AccessibilityEvent.obtain()); } @MediumTest Loading