diff options
| author | Phil Weaver <pweaver@google.com> | 2016-08-04 10:37:17 -0700 |
|---|---|---|
| committer | Phil Weaver <pweaver@google.com> | 2016-08-19 09:22:10 -0700 |
| commit | c34649411d053185b3572c4cd924e6f14295d8cd (patch) | |
| tree | 1cffdf4a770ac42a138102c813870961daf85d58 /core/java/android | |
| parent | 4ecc9d2db400225b6f5e0d76d17b8f94354996a6 (diff) | |
Dispatch a11y events in separate thread.
Moves the IPCs into a separate thread, where they should affect
jank a lot less.
Bug: 30183085
Change-Id: Ib76159d158e7a867e76cdd5c8ea3a318949fcc5b
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityManager.java | 164 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/IAccessibilityManager.aidl | 5 |
2 files changed, 129 insertions, 40 deletions
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 2dfa8cdd3db9..44f6facd88f5 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -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; @@ -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; @@ -99,6 +103,8 @@ public final class AccessibilityManager { private IAccessibilityManager mService; + private EventDispatchThread mEventDispatchThread; + final int mUserId; final Handler mHandler; @@ -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 @@ -297,47 +303,32 @@ public final class AccessibilityManager { * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { + if (!isEnabled()) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } 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; } - if (!mIsEnabled) { - Looper myLooper = Looper.myLooper(); - if (myLooper == Looper.getMainLooper()) { - throw new IllegalStateException( - "Accessibility off. Did you forget to check that?"); - } 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; - } - } - 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(); + event.setEventTime(SystemClock.uptimeMillis()); + + getEventDispatchThread().scheduleEvent(event); + } + + private EventDispatchThread getEventDispatchThread() { + synchronized (mLock) { + if (mEventDispatchThread == null) { + mEventDispatchThread = new EventDispatchThread(mService, mUserId); + mEventDispatchThread.start(); } + return mEventDispatchThread; } } @@ -620,7 +611,7 @@ public final class AccessibilityManager { } } - private IAccessibilityManager getServiceLocked() { + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); } @@ -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(); + } + } + } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 7f44bac8bc6f..aa9cb39062f9 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -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; @@ -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); |
