summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorPhil Weaver <pweaver@google.com>2016-08-04 10:37:17 -0700
committerPhil Weaver <pweaver@google.com>2016-08-19 09:22:10 -0700
commitc34649411d053185b3572c4cd924e6f14295d8cd (patch)
tree1cffdf4a770ac42a138102c813870961daf85d58 /core/java/android
parent4ecc9d2db400225b6f5e0d76d17b8f94354996a6 (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.java164
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl5
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);