Merge "Allow a11y listeners to specify handlers" into oc-dev
diff --git a/api/current.txt b/api/current.txt
index c7fd3dc..ae4d838 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -47149,7 +47149,9 @@
 
   public final class AccessibilityManager {
     method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
     method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+    method public void addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler);
     method public deprecated java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
diff --git a/api/system-current.txt b/api/system-current.txt
index 78f911a..f1429f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -50734,7 +50734,9 @@
 
   public final class AccessibilityManager {
     method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
     method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+    method public void addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler);
     method public deprecated java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4ad78c5..c15836b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -47526,7 +47526,9 @@
 
   public final class AccessibilityManager {
     method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
     method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+    method public void addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler);
     method public deprecated java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9ecced6..1f13220 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -435,8 +435,9 @@
 
     AccessibilityInteractionController mAccessibilityInteractionController;
 
-    AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
-    HighContrastTextManager mHighContrastTextManager;
+    final AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager =
+            new AccessibilityInteractionConnectionManager();
+    final HighContrastTextManager mHighContrastTextManager;
 
     SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
 
@@ -496,13 +497,11 @@
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                 context);
         mAccessibilityManager = AccessibilityManager.getInstance(context);
-        mAccessibilityInteractionConnectionManager =
-            new AccessibilityInteractionConnectionManager();
         mAccessibilityManager.addAccessibilityStateChangeListener(
-                mAccessibilityInteractionConnectionManager);
+                mAccessibilityInteractionConnectionManager, mHandler);
         mHighContrastTextManager = new HighContrastTextManager();
         mAccessibilityManager.addHighTextContrastStateChangeListener(
-                mHighContrastTextManager);
+                mHighContrastTextManager, mHandler);
         mViewConfiguration = ViewConfiguration.get(context);
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
         mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 6c884c4..dfb0095 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -37,6 +37,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.IWindow;
 import android.view.View;
@@ -47,7 +48,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
@@ -137,17 +137,18 @@
 
     boolean mIsHighTextContrastEnabled;
 
-    private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
-            mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();
+    private final ArrayMap<AccessibilityStateChangeListener, Handler>
+            mAccessibilityStateChangeListeners = new ArrayMap<>();
 
-    private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
-            mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<>();
+    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+            mTouchExplorationStateChangeListeners = new ArrayMap<>();
 
-    private final CopyOnWriteArrayList<HighTextContrastChangeListener>
-            mHighTextContrastStateChangeListeners = new CopyOnWriteArrayList<>();
+    private final ArrayMap<HighTextContrastChangeListener, Handler>
+            mHighTextContrastStateChangeListeners = new ArrayMap<>();
 
-    private final CopyOnWriteArrayList<AccessibilityServicesStateChangeListener>
-            mServicesStateChangeListeners = new CopyOnWriteArrayList<>();
+    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+            mServicesStateChangeListeners = new ArrayMap<>();
+
     /**
      * Listener for the system accessibility state. To listen for changes to the
      * accessibility state on the device, implement this interface and register
@@ -229,7 +230,21 @@
 
         @Override
         public void notifyServicesStateChanged() {
-            mHandler.obtainMessage(MyCallback.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget();
+            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+            synchronized (mLock) {
+                if (mServicesStateChangeListeners.isEmpty()) {
+                    return;
+                }
+                listeners = new ArrayMap<>(mServicesStateChangeListeners);
+            }
+
+            int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                final AccessibilityServicesStateChangeListener listener =
+                        mServicesStateChangeListeners.keyAt(i);
+                mServicesStateChangeListeners.valueAt(i).post(() -> listener
+                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+            }
         }
 
         @Override
@@ -565,73 +580,118 @@
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system.
+     * the global accessibility state of the system. Equivalent to calling
+     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addAccessibilityStateChangeListener(
             @NonNull AccessibilityStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mAccessibilityStateChangeListeners.add(listener);
+        addAccessibilityStateChangeListener(listener, null);
+        return true;
+    }
+
+    /**
+     * Registers an {@link AccessibilityStateChangeListener} for changes in
+     * the global accessibility state of the system. If the listener has already been registered,
+     * the handler used to call it back is updated.
+     *
+     * @param listener The listener.
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
+     */
+    public void addAccessibilityStateChangeListener(
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mAccessibilityStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
     }
 
     /**
      * Unregisters an {@link AccessibilityStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
+     * @return True if the listener was previously registered.
      */
     public boolean removeAccessibilityStateChangeListener(
             @NonNull AccessibilityStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mAccessibilityStateChangeListeners.remove(listener);
+        synchronized (mLock) {
+            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+            mAccessibilityStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system.
+     * the global touch exploration state of the system. Equivalent to calling
+     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mTouchExplorationStateChangeListeners.add(listener);
+        addTouchExplorationStateChangeListener(listener, null);
+        return true;
+    }
+
+    /**
+     * Registers an {@link TouchExplorationStateChangeListener} for changes in
+     * the global touch exploration state of the system. If the listener has already been
+     * registered, the handler used to call it back is updated.
+     *
+     * @param listener The listener.
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
+     */
+    public void addTouchExplorationStateChangeListener(
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mTouchExplorationStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
     }
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
+     * @return True if listener was previously registered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mTouchExplorationStateChangeListeners.remove(listener);
+        synchronized (mLock) {
+            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+            mTouchExplorationStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
     }
 
     /**
      * Registers a {@link AccessibilityServicesStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
-     *
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
      * @hide
      */
     public void addAccessibilityServicesStateChangeListener(
-            @NonNull AccessibilityServicesStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        mServicesStateChangeListeners.add(listener);
+            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mServicesStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
     }
 
     /**
      * Unregisters a {@link AccessibilityServicesStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
      *
      * @hide
      */
@@ -646,28 +706,29 @@
      * the global high text contrast state of the system.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
      *
      * @hide
      */
-    public boolean addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mHighTextContrastStateChangeListeners.add(listener);
+    public void addHighTextContrastStateChangeListener(
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
     }
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
      *
      * @hide
      */
-    public boolean removeHighTextContrastStateChangeListener(
+    public void removeHighTextContrastStateChangeListener(
             @NonNull HighTextContrastChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        return mHighTextContrastStateChangeListeners.remove(listener);
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners.remove(listener);
+        }
     }
 
     /**
@@ -732,15 +793,15 @@
         mIsHighTextContrastEnabled = highTextContrastEnabled;
 
         if (wasEnabled != enabled) {
-            mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
+            notifyAccessibilityStateChanged();
         }
 
         if (wasTouchExplorationEnabled != touchExplorationEnabled) {
-            mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
+            notifyTouchExplorationStateChanged();
         }
 
         if (wasHighTextContrastEnabled != highTextContrastEnabled) {
-            mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
+            notifyHighTextContrastStateChanged();
         }
     }
 
@@ -932,81 +993,78 @@
     /**
      * Notifies the registered {@link AccessibilityStateChangeListener}s.
      */
-    private void handleNotifyAccessibilityStateChanged() {
+    private void notifyAccessibilityStateChanged() {
         final boolean isEnabled;
+        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
         synchronized (mLock) {
+            if (mAccessibilityStateChangeListeners.isEmpty()) {
+                return;
+            }
             isEnabled = mIsEnabled;
+            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
         }
-        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
-        for (AccessibilityStateChangeListener listener : mAccessibilityStateChangeListeners) {
-            listener.onAccessibilityStateChanged(isEnabled);
+
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final AccessibilityStateChangeListener listener =
+                    mAccessibilityStateChangeListeners.keyAt(i);
+            mAccessibilityStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onAccessibilityStateChanged(isEnabled));
         }
     }
 
     /**
      * Notifies the registered {@link TouchExplorationStateChangeListener}s.
      */
-    private void handleNotifyTouchExplorationStateChanged() {
+    private void notifyTouchExplorationStateChanged() {
         final boolean isTouchExplorationEnabled;
+        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
         synchronized (mLock) {
+            if (mTouchExplorationStateChangeListeners.isEmpty()) {
+                return;
+            }
             isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
         }
-        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
-        for (TouchExplorationStateChangeListener listener :mTouchExplorationStateChangeListeners) {
-            listener.onTouchExplorationStateChanged(isTouchExplorationEnabled);
+
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final TouchExplorationStateChangeListener listener =
+                    mTouchExplorationStateChangeListeners.keyAt(i);
+            mTouchExplorationStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
         }
     }
 
     /**
      * Notifies the registered {@link HighTextContrastChangeListener}s.
      */
-    private void handleNotifyHighTextContrastStateChanged() {
+    private void notifyHighTextContrastStateChanged() {
         final boolean isHighTextContrastEnabled;
+        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
         synchronized (mLock) {
+            if (mHighTextContrastStateChangeListeners.isEmpty()) {
+                return;
+            }
             isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
         }
-        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
-        for (HighTextContrastChangeListener listener : mHighTextContrastStateChangeListeners) {
-            listener.onHighTextContrastStateChanged(isHighTextContrastEnabled);
-        }
-    }
 
-    /**
-     * Notifies the registered {@link AccessibilityServicesStateChangeListener}s.
-     */
-    private void handleNotifyServicesStateChanged() {
-        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
-        for (AccessibilityServicesStateChangeListener listener : mServicesStateChangeListeners) {
-            listener.onAccessibilityServicesStateChanged(this);
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final HighTextContrastChangeListener listener =
+                    mHighTextContrastStateChangeListeners.keyAt(i);
+            mHighTextContrastStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
         }
     }
 
     private final class MyCallback implements Handler.Callback {
-        public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
-        public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
-        public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
-        public static final int MSG_SET_STATE = 4;
-        public static final int MSG_NOTIFY_SERVICES_STATE_CHANGED = 5;
+        public static final int MSG_SET_STATE = 1;
 
         @Override
         public boolean handleMessage(Message message) {
             switch (message.what) {
-                case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
-                    handleNotifyAccessibilityStateChanged();
-                } break;
-
-                case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
-                    handleNotifyTouchExplorationStateChanged();
-                } break;
-
-                case MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED: {
-                    handleNotifyHighTextContrastStateChanged();
-                } break;
-
-                case MSG_NOTIFY_SERVICES_STATE_CHANGED: {
-                    handleNotifyServicesStateChanged();
-                } break;
-
                 case MSG_SET_STATE: {
                     // See comment at mClient
                     final int state = message.arg1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index dfa5cbd..6a573f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -19,7 +19,7 @@
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
 /**
- * For mocking because AccessibilyManager is final for some reason...
+ * For mocking because AccessibilityManager is final for some reason...
  */
 public class AccessibilityManagerWrapper implements
         CallbackController<AccessibilityServicesStateChangeListener> {
@@ -32,7 +32,7 @@
 
     @Override
     public void addCallback(AccessibilityServicesStateChangeListener listener) {
-        mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
+        mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index eea5473..457cf87 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6044,8 +6044,8 @@
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
         updateA11yVolumeAlias(accessibilityManager.isAccessibilityVolumeStreamActive());
-        accessibilityManager.addTouchExplorationStateChangeListener(this);
-        accessibilityManager.addAccessibilityServicesStateChangeListener(this);
+        accessibilityManager.addTouchExplorationStateChangeListener(this, null);
+        accessibilityManager.addAccessibilityServicesStateChangeListener(this, null);
     }
 
     //---------------------------------------------------------------------------------