Revert "Move A11y events throttling away from View(RootImpl)"

This reverts commit e4d31b3c103045d5b2b141a05084dced595cc64f.

Fixes: 71904218
Test: presubmit
Change-Id: Id73bde1a0c11696cf561c84cde027cdca4c6a00f
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index ba39740..8f01685 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -46,14 +47,10 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
-
-import com.android.internal.util.CollectionUtils;
-
 import libcore.io.IoUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
@@ -583,8 +580,6 @@
         // Execute the command *without* the lock being held.
         command.run();
 
-        List<AccessibilityEvent> eventsReceived = Collections.emptyList();
-
         // Acquire the lock and wait for the event.
         try {
             // Wait for the event.
@@ -605,14 +600,14 @@
                     if (filter.accept(event)) {
                         return event;
                     }
-                    eventsReceived = CollectionUtils.add(eventsReceived, event);
+                    event.recycle();
                 }
                 // Check if timed out and if not wait.
                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                 if (remainingTimeMillis <= 0) {
                     throw new TimeoutException("Expected event not received within: "
-                            + timeoutMillis + " ms, among " + eventsReceived);
+                            + timeoutMillis + " ms.");
                 }
                 synchronized (mLock) {
                     if (mEventQueue.isEmpty()) {
@@ -625,10 +620,6 @@
                 }
             }
         } finally {
-            for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) {
-                AccessibilityEvent event = eventsReceived.get(i);
-                event.recycle();
-            }
             synchronized (mLock) {
                 mWaitingForEventDelivery = false;
                 mEventQueue.clear();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3d6a6fe..5150c1f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4442,6 +4442,7 @@
     private CheckForLongPress mPendingCheckForLongPress;
     private CheckForTap mPendingCheckForTap = null;
     private PerformClick mPerformClick;
+    private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
 
     private UnsetPressedState mUnsetPressedState;
 
@@ -7201,7 +7202,7 @@
         if (gainFocus) {
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         } else {
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
 
@@ -7271,7 +7272,8 @@
     public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) {
         if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) {
             mAccessibilityPaneTitle = accessibilityPaneTitle;
-            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE);
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE);
         }
     }
 
@@ -8924,9 +8926,9 @@
         final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0;
         if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         } else {
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
         }
     }
@@ -8959,7 +8961,8 @@
             return;
         }
         mAccessibilityTraversalBeforeId = beforeId;
-        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -9002,7 +9005,8 @@
             return;
         }
         mAccessibilityTraversalAfterId = afterId;
-        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -9044,7 +9048,8 @@
                 && mID == View.NO_ID) {
             mID = generateViewId();
         }
-        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -10544,7 +10549,8 @@
 
         if (pflags3 != mPrivateFlags3) {
             mPrivateFlags3 = pflags3;
-            notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -11374,7 +11380,8 @@
             mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
             mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
                     & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
-            notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -11431,9 +11438,10 @@
             mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
                     & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
             if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
-                notifyAccessibilitySubtreeChanged();
+                notifySubtreeAccessibilityStateChangedIfNeeded();
             } else {
-                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -11609,32 +11617,11 @@
      *
      * @hide
      */
-    public void notifyAccessibilityStateChanged(int changeType) {
-        notifyAccessibilityStateChanged(this, changeType);
-    }
-
-    /**
-     * Notifies that the accessibility state of this view changed. The change
-     * is *not* local to this view and does represent structural changes such
-     * as children and parent. For example, the view size changed. The
-     * notification is at at most once every
-     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
-     * to avoid unnecessary load to the system. Also once a view has a pending
-     * notification this method is a NOP until the notification has been sent.
-     *
-     * @hide
-     */
-    public void notifyAccessibilitySubtreeChanged() {
-        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
-            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
-            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-        }
-    }
-
-    void notifyAccessibilityStateChanged(View source, int changeType) {
+    public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
+
         // Changes to views with a pane title count as window state changes, as the pane title
         // marks them as significant parts of the UI.
         if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
@@ -11652,12 +11639,49 @@
             }
         }
 
-        if (mParent != null) {
+        // If this is a live region, we should send a subtree change event
+        // from this view immediately. Otherwise, we can let it propagate up.
+        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+            final AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            event.setContentChangeTypes(changeType);
+            sendAccessibilityEventUnchecked(event);
+        } else if (mParent != null) {
             try {
-                mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+                mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
             } catch (AbstractMethodError e) {
-                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName()
-                        + " does not fully implement ViewParent", e);
+                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                        " does not fully implement ViewParent", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies that the accessibility state of this view changed. The change
+     * is *not* local to this view and does represent structural changes such
+     * as children and parent. For example, the view size changed. The
+     * notification is at at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+     * to avoid unnecessary load to the system. Also once a view has a pending
+     * notification this method is a NOP until the notification has been sent.
+     *
+     * @hide
+     */
+    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+            return;
+        }
+
+        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
+            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+            if (mParent != null) {
+                try {
+                    mParent.notifySubtreeAccessibilityStateChanged(
+                            this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                } catch (AbstractMethodError e) {
+                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                            " does not fully implement ViewParent", e);
+                }
             }
         }
     }
@@ -11679,10 +11703,8 @@
     /**
      * Reset the flag indicating the accessibility state of the subtree rooted
      * at this view changed.
-     *
-     * @hide
      */
-    public void resetSubtreeAccessibilityStateChanged() {
+    void resetSubtreeAccessibilityStateChanged() {
         mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
     }
 
@@ -11843,7 +11865,8 @@
                         || getAccessibilitySelectionEnd() != end)
                         && (start == end)) {
                     setAccessibilitySelection(start, end);
-                    notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+                    notifyViewAccessibilityStateChangedIfNeeded(
+                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
                     return true;
                 }
             } break;
@@ -12643,7 +12666,7 @@
         }
         if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
             if (isVisible != oldVisible) {
-                notifyAccessibilityStateChanged(isVisible
+                notifyViewAccessibilityStateChangedIfNeeded(isVisible
                         ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
                         : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
             }
@@ -13860,7 +13883,7 @@
                         ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
                     dispatchVisibilityAggregated(newVisibility == VISIBLE);
                 }
-                notifyAccessibilitySubtreeChanged();
+                notifySubtreeAccessibilityStateChangedIfNeeded();
             }
         }
 
@@ -13906,12 +13929,14 @@
                     || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
                     || (changed & CONTEXT_CLICKABLE) != 0) {
                 if (oldIncludeForAccessibility != includeForAccessibility()) {
-                    notifyAccessibilitySubtreeChanged();
+                    notifySubtreeAccessibilityStateChangedIfNeeded();
                 } else {
-                    notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+                    notifyViewAccessibilityStateChangedIfNeeded(
+                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
                 }
             } else if ((changed & ENABLED_MASK) != 0) {
-                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -13945,13 +13970,10 @@
      * @param oldt Previous vertical scroll origin.
      */
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        notifyAccessibilitySubtreeChanged();
+        notifySubtreeAccessibilityStateChangedIfNeeded();
 
-        ViewRootImpl root = getViewRootImpl();
-        if (root != null) {
-            root.getAccessibilityState()
-                    .getSendViewScrolledAccessibilityEvent()
-                    .post(this, /* dx */ l - oldl, /* dy */ t - oldt);
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
         }
 
         mBackgroundSizeChanged = true;
@@ -14347,7 +14369,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -14391,7 +14413,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -14435,7 +14457,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -14472,7 +14494,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -14509,7 +14531,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -14712,7 +14734,7 @@
         if (mTransformationInfo.mAlpha != alpha) {
             // Report visibility changes, which can affect children, to accessibility
             if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
-                notifyAccessibilitySubtreeChanged();
+                notifySubtreeAccessibilityStateChangedIfNeeded();
             }
             mTransformationInfo.mAlpha = alpha;
             if (onSetAlpha((int) (alpha * 255))) {
@@ -15214,7 +15236,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -15248,7 +15270,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -15418,7 +15440,7 @@
     public void invalidateOutline() {
         rebuildOutline();
 
-        notifyAccessibilitySubtreeChanged();
+        notifySubtreeAccessibilityStateChangedIfNeeded();
         invalidateViewProperty(false, false);
     }
 
@@ -15613,7 +15635,7 @@
                 }
                 invalidateParentIfNeeded();
             }
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -15661,7 +15683,7 @@
                 }
                 invalidateParentIfNeeded();
             }
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
@@ -16539,6 +16561,18 @@
     }
 
     /**
+     * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+     * This event is sent at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+     */
+    private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
+        if (mSendViewScrolledAccessibilityEvent == null) {
+            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+        }
+        mSendViewScrolledAccessibilityEvent.post(dx, dy);
+    }
+
+    /**
      * Called by a parent to request that a child update its values for mScrollX
      * and mScrollY if necessary. This will typically be done if the child is
      * animating a scroll using a {@link android.widget.Scroller Scroller}
@@ -17793,13 +17827,7 @@
         removeUnsetPressCallback();
         removeLongPressCallback();
         removePerformClickCallback();
-        if (mAttachInfo != null
-                && mAttachInfo.mViewRootImpl.mAccessibilityState != null
-                && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) {
-            mAttachInfo.mViewRootImpl.mAccessibilityState
-                    .getSendViewScrolledAccessibilityEvent()
-                    .cancelIfPendingFor(this);
-        }
+        cancel(mSendViewScrolledAccessibilityEvent);
         stopNestedScroll();
 
         // Anything that started animating right before detach should already
@@ -20427,7 +20455,7 @@
                 mForegroundInfo.mBoundsChanged = true;
             }
 
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
         return changed;
     }
@@ -21871,7 +21899,8 @@
             if (selected) {
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
             } else {
-                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -26458,6 +26487,53 @@
     }
 
     /**
+     * Resuable callback for sending
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private class SendViewScrolledAccessibilityEvent implements Runnable {
+        public volatile boolean mIsPending;
+        public int mDeltaX;
+        public int mDeltaY;
+
+        public void post(int dx, int dy) {
+            mDeltaX += dx;
+            mDeltaY += dy;
+            if (!mIsPending) {
+                mIsPending = true;
+                postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+            }
+        }
+
+        @Override
+        public void run() {
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                AccessibilityEvent event = AccessibilityEvent.obtain(
+                        AccessibilityEvent.TYPE_VIEW_SCROLLED);
+                event.setScrollDeltaX(mDeltaX);
+                event.setScrollDeltaY(mDeltaY);
+                sendAccessibilityEventUnchecked(event);
+            }
+            reset();
+        }
+
+        private void reset() {
+            mIsPending = false;
+            mDeltaX = 0;
+            mDeltaY = 0;
+        }
+    }
+
+    /**
+     * Remove the pending callback for sending a
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) {
+        if (callback == null || !callback.mIsPending) return;
+        removeCallbacks(callback);
+        callback.reset();
+    }
+
+    /**
      * <p>
      * This class represents a delegate that can be registered in a {@link View}
      * to enhance accessibility support via composition rather via inheritance.
@@ -27074,7 +27150,7 @@
         mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
         mAttachInfo.mTooltipHost = this;
         // The available accessibility actions have changed
-        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED);
         return true;
     }
 
@@ -27094,7 +27170,7 @@
             mAttachInfo.mTooltipHost = null;
         }
         // The available accessibility actions have changed
-        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     private boolean showLongClickTooltip(int x, int y) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 4631261..a8bdb85 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3649,34 +3649,44 @@
         return ViewGroup.class.getName();
     }
 
+    @Override
+    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+        // If this is a live region, we should send a subtree change event
+        // from this view. Otherwise, we can let it propagate up.
+        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+        } else if (mParent != null) {
+            try {
+                mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+            } catch (AbstractMethodError e) {
+                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+                        " does not fully implement ViewParent", e);
+            }
+        }
+    }
+
     /** @hide */
     @Override
-    public void notifyAccessibilitySubtreeChanged() {
+    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
         // If something important for a11y is happening in this subtree, make sure it's dispatched
         // from a view that is important for a11y so it doesn't get lost.
-        if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                && !isImportantForAccessibility()
-                && getChildCount() > 0) {
+        if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+                && !isImportantForAccessibility() && (getChildCount() > 0)) {
             ViewParent a11yParent = getParentForAccessibility();
             if (a11yParent instanceof View) {
-                ((View) a11yParent).notifyAccessibilitySubtreeChanged();
+                ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
                 return;
             }
         }
-        super.notifyAccessibilitySubtreeChanged();
+        super.notifySubtreeAccessibilityStateChangedIfNeeded();
     }
 
     @Override
-    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
-        notifyAccessibilityStateChanged(source, changeType);
-    }
-
-    /** @hide */
-    @Override
-    public void resetSubtreeAccessibilityStateChanged() {
+    void resetSubtreeAccessibilityStateChanged() {
         super.resetSubtreeAccessibilityStateChanged();
         View[] children = mChildren;
         final int childCount = mChildrenCount;
@@ -5088,7 +5098,7 @@
         }
 
         if (child.getVisibility() != View.GONE) {
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
 
         if (mTransientIndices != null) {
@@ -5358,7 +5368,7 @@
         dispatchViewRemoved(view);
 
         if (view.getVisibility() != View.GONE) {
-            notifyAccessibilitySubtreeChanged();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
 
         int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
@@ -6077,7 +6087,7 @@
         if (invalidate) {
             invalidateViewProperty(false, false);
         }
-        notifyAccessibilitySubtreeChanged();
+        notifySubtreeAccessibilityStateChangedIfNeeded();
     }
 
     @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 30f584c..29246fd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,11 +89,9 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.accessibility.AccessibilityViewHierarchyState;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-import android.view.accessibility.ThrottlingAccessibilityEventSender;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -115,6 +113,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -461,6 +460,10 @@
             new AccessibilityInteractionConnectionManager();
     final HighContrastTextManager mHighContrastTextManager;
 
+    SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
+
+    HashSet<View> mTempHashSet;
+
     private final int mDensity;
     private final int mNoncompatDensity;
 
@@ -475,8 +478,6 @@
 
     private boolean mNeedsRendererSetup;
 
-    protected AccessibilityViewHierarchyState mAccessibilityState;
-
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -7258,9 +7259,11 @@
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
      */
     private void postSendWindowContentChangedCallback(View source, int changeType) {
-        getAccessibilityState()
-                .getSendWindowContentChangedAccessibilityEvent()
-                .runOrPost(source, changeType);
+        if (mSendWindowContentChangedAccessibilityEvent == null) {
+            mSendWindowContentChangedAccessibilityEvent =
+                new SendWindowContentChangedAccessibilityEvent();
+        }
+        mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
     }
 
     /**
@@ -7268,20 +7271,11 @@
      * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
      */
     private void removeSendWindowContentChangedCallback() {
-        if (mAccessibilityState != null
-                && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
-            ThrottlingAccessibilityEventSender.cancelIfPending(
-                    mAccessibilityState.getSendWindowContentChangedAccessibilityEvent());
+        if (mSendWindowContentChangedAccessibilityEvent != null) {
+            mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
         }
     }
 
-    AccessibilityViewHierarchyState getAccessibilityState() {
-        if (mAccessibilityState == null) {
-            mAccessibilityState = new AccessibilityViewHierarchyState();
-        }
-        return mAccessibilityState;
-    }
-
     @Override
     public boolean showContextMenuForChild(View originalView) {
         return false;
@@ -7317,8 +7311,12 @@
             return false;
         }
 
-        // Send any pending event to prevent reordering
-        flushPendingAccessibilityEvents();
+        // Immediately flush pending content changed event (if any) to preserve event order
+        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                && mSendWindowContentChangedAccessibilityEvent != null
+                && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
+            mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
+        }
 
         // Intercept accessibility focus events fired by virtual nodes to keep
         // track of accessibility focus position in such nodes.
@@ -7362,19 +7360,6 @@
         return true;
     }
 
-    /** @hide */
-    public void flushPendingAccessibilityEvents() {
-        if (mAccessibilityState != null) {
-            if (mAccessibilityState.isScrollEventSenderInitialized()) {
-                mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending();
-            }
-            if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
-                mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()
-                        .sendNowIfPending();
-            }
-        }
-    }
-
     /**
      * Updates the focused virtual view, when necessary, in response to a
      * content changed event.
@@ -7509,6 +7494,39 @@
         return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
     }
 
+    private View getCommonPredecessor(View first, View second) {
+        if (mTempHashSet == null) {
+            mTempHashSet = new HashSet<View>();
+        }
+        HashSet<View> seen = mTempHashSet;
+        seen.clear();
+        View firstCurrent = first;
+        while (firstCurrent != null) {
+            seen.add(firstCurrent);
+            ViewParent firstCurrentParent = firstCurrent.mParent;
+            if (firstCurrentParent instanceof View) {
+                firstCurrent = (View) firstCurrentParent;
+            } else {
+                firstCurrent = null;
+            }
+        }
+        View secondCurrent = second;
+        while (secondCurrent != null) {
+            if (seen.contains(secondCurrent)) {
+                seen.clear();
+                return secondCurrent;
+            }
+            ViewParent secondCurrentParent = secondCurrent.mParent;
+            if (secondCurrentParent instanceof View) {
+                secondCurrent = (View) secondCurrentParent;
+            } else {
+                secondCurrent = null;
+            }
+        }
+        seen.clear();
+        return null;
+    }
+
     void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
@@ -8119,6 +8137,80 @@
         }
     }
 
+    private class SendWindowContentChangedAccessibilityEvent implements Runnable {
+        private int mChangeTypes = 0;
+
+        public View mSource;
+        public long mLastEventTimeMillis;
+
+        @Override
+        public void run() {
+            // Protect against re-entrant code and attempt to do the right thing in the case that
+            // we're multithreaded.
+            View source = mSource;
+            mSource = null;
+            if (source == null) {
+                Log.e(TAG, "Accessibility content change has no source");
+                return;
+            }
+            // The accessibility may be turned off while we were waiting so check again.
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                mLastEventTimeMillis = SystemClock.uptimeMillis();
+                AccessibilityEvent event = AccessibilityEvent.obtain();
+                event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                event.setContentChangeTypes(mChangeTypes);
+                source.sendAccessibilityEventUnchecked(event);
+            } else {
+                mLastEventTimeMillis = 0;
+            }
+            // In any case reset to initial state.
+            source.resetSubtreeAccessibilityStateChanged();
+            mChangeTypes = 0;
+        }
+
+        public void runOrPost(View source, int changeType) {
+            if (mHandler.getLooper() != Looper.myLooper()) {
+                CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the "
+                        + "original thread that created a view hierarchy can touch its views.");
+                // TODO: Throw the exception
+                Log.e(TAG, "Accessibility content change on non-UI thread. Future Android "
+                        + "versions will throw an exception.", e);
+                // Attempt to recover. This code does not eliminate the thread safety issue, but
+                // it should force any issues to happen near the above log.
+                mHandler.removeCallbacks(this);
+                if (mSource != null) {
+                    // Dispatch whatever was pending. It's still possible that the runnable started
+                    // just before we removed the callbacks, and bad things will happen, but at
+                    // least they should happen very close to the logged error.
+                    run();
+                }
+            }
+            if (mSource != null) {
+                // If there is no common predecessor, then mSource points to
+                // a removed view, hence in this case always prefer the source.
+                View predecessor = getCommonPredecessor(mSource, source);
+                mSource = (predecessor != null) ? predecessor : source;
+                mChangeTypes |= changeType;
+                return;
+            }
+            mSource = source;
+            mChangeTypes = changeType;
+            final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
+            final long minEventIntevalMillis =
+                    ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+            if (timeSinceLastMillis >= minEventIntevalMillis) {
+                removeCallbacksAndRun();
+            } else {
+                mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
+            }
+        }
+
+        public void removeCallbacksAndRun() {
+            mHandler.removeCallbacks(this);
+            run();
+        }
+    }
+
     private static class KeyFallbackManager {
 
         // This is used to ensure that key-fallback events are only dispatched once. We attempt
diff --git a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
deleted file mode 100644
index 447fafa..0000000
--- a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-/**
- * Accessibility-related state of a {@link android.view.ViewRootImpl}
- *
- * @hide
- */
-public class AccessibilityViewHierarchyState {
-    private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
-    private @Nullable SendWindowContentChangedAccessibilityEvent
-            mSendWindowContentChangedAccessibilityEvent;
-
-    /**
-     * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed
-     */
-    public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() {
-        if (mSendViewScrolledAccessibilityEvent == null) {
-            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
-        }
-        return mSendViewScrolledAccessibilityEvent;
-    }
-
-    public boolean isScrollEventSenderInitialized() {
-        return mSendViewScrolledAccessibilityEvent != null;
-    }
-
-    /**
-     * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed
-     */
-    public @NonNull SendWindowContentChangedAccessibilityEvent
-            getSendWindowContentChangedAccessibilityEvent() {
-        if (mSendWindowContentChangedAccessibilityEvent == null) {
-            mSendWindowContentChangedAccessibilityEvent =
-                    new SendWindowContentChangedAccessibilityEvent();
-        }
-        return mSendWindowContentChangedAccessibilityEvent;
-    }
-
-    public boolean isWindowContentChangedEventSenderInitialized() {
-        return mSendWindowContentChangedAccessibilityEvent != null;
-    }
-}
diff --git a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
deleted file mode 100644
index 40a1b6a..0000000
--- a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility;
-
-
-import android.annotation.NonNull;
-import android.view.View;
-
-/**
- * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
- *
- * @hide
- */
-public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender {
-
-    public int mDeltaX;
-    public int mDeltaY;
-
-    /**
-     * Post a scroll event to be sent for the given view
-     */
-    public void post(View source, int dx, int dy) {
-        if (!isPendingFor(source)) sendNowIfPending();
-
-        mDeltaX += dx;
-        mDeltaY += dy;
-
-        if (!isPendingFor(source)) scheduleFor(source);
-    }
-
-    @Override
-    protected void performSendEvent(@NonNull View source) {
-        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-        event.setScrollDeltaX(mDeltaX);
-        event.setScrollDeltaY(mDeltaY);
-        source.sendAccessibilityEventUnchecked(event);
-    }
-
-    @Override
-    protected void resetState(@NonNull View source) {
-        mDeltaX = 0;
-        mDeltaY = 0;
-    }
-}
diff --git a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
deleted file mode 100644
index df38fba..0000000
--- a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility;
-
-
-import static com.android.internal.util.ObjectUtils.firstNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.View;
-import android.view.ViewParent;
-
-import java.util.HashSet;
-
-/**
- * @hide
- */
-public class SendWindowContentChangedAccessibilityEvent
-        extends ThrottlingAccessibilityEventSender {
-
-    private int mChangeTypes = 0;
-
-    private HashSet<View> mTempHashSet;
-
-    @Override
-    protected void performSendEvent(@NonNull View source) {
-        AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        event.setContentChangeTypes(mChangeTypes);
-        source.sendAccessibilityEventUnchecked(event);
-    }
-
-    @Override
-    protected void resetState(@Nullable View source) {
-        if (source != null) {
-            source.resetSubtreeAccessibilityStateChanged();
-        }
-        mChangeTypes = 0;
-    }
-
-    /**
-     * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given
-     * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view
-     */
-    public void runOrPost(View source, int changeType) {
-        if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) {
-            sendNowIfPending();
-            mChangeTypes = changeType;
-            sendNow(source);
-        } else {
-            mChangeTypes |= changeType;
-            scheduleFor(source);
-        }
-    }
-
-    @Override
-    protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
-        // If there is no common predecessor, then oldSource points to
-        // a removed view, hence in this case always prefer the newSource.
-        return firstNotNull(
-                getCommonPredecessor(oldSource, newSource),
-                newSource);
-    }
-
-    private View getCommonPredecessor(View first, View second) {
-        if (mTempHashSet == null) {
-            mTempHashSet = new HashSet<>();
-        }
-        HashSet<View> seen = mTempHashSet;
-        seen.clear();
-        View firstCurrent = first;
-        while (firstCurrent != null) {
-            seen.add(firstCurrent);
-            ViewParent firstCurrentParent = firstCurrent.getParent();
-            if (firstCurrentParent instanceof View) {
-                firstCurrent = (View) firstCurrentParent;
-            } else {
-                firstCurrent = null;
-            }
-        }
-        View secondCurrent = second;
-        while (secondCurrent != null) {
-            if (seen.contains(secondCurrent)) {
-                seen.clear();
-                return secondCurrent;
-            }
-            ViewParent secondCurrentParent = secondCurrent.getParent();
-            if (secondCurrentParent instanceof View) {
-                secondCurrent = (View) secondCurrentParent;
-            } else {
-                secondCurrent = null;
-            }
-        }
-        seen.clear();
-        return null;
-    }
-}
diff --git a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
deleted file mode 100644
index 66fa301..0000000
--- a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility;
-
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.CalledFromWrongThreadException;
-
-/**
- * A throttling {@link AccessibilityEvent} sender that relies on its currently associated
- * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly
- * {@link #tryMerge merge} together any events that come in less than
- * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval
- * the configured amount of milliseconds} apart.
- *
- * The suggested usage is to create a singleton extending this class, holding any state specific to
- * the particular event type that the subclass represents, and have an 'entrypoint' method that
- * delegates to {@link #scheduleFor(View)}.
- * For example:
- *
- * {@code
- *     public void post(View view, String text, int resId) {
- *         mText = text;
- *         mId = resId;
- *         scheduleFor(view);
- *     }
- * }
- *
- * @see #scheduleFor(View)
- * @see #tryMerge(View, View)
- * @see #performSendEvent(View)
- * @hide
- */
-public abstract class ThrottlingAccessibilityEventSender {
-
-    private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "ThrottlingA11ySender";
-
-    View mSource;
-    private long mLastSendTimeMillis = Long.MIN_VALUE;
-    private boolean mIsPending = false;
-
-    private final Runnable mWorker = () -> {
-        View source = mSource;
-        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")");
-
-        if (!checkAndResetIsPending() || source == null) {
-            resetStateInternal();
-            return;
-        }
-
-        // Accessibility may be turned off while we were waiting
-        if (isAccessibilityEnabled(source)) {
-            mLastSendTimeMillis = SystemClock.uptimeMillis();
-            performSendEvent(source);
-        }
-        resetStateInternal();
-    };
-
-    /**
-     * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well
-     * as any extra data from this instance's state.
-     *
-     * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or
-     * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for
-     * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that
-     * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any.
-     */
-    protected abstract void performSendEvent(@NonNull View source);
-
-    /**
-     * Perform optional cleanup after {@link #performSendEvent}
-     *
-     * @param source the view this event was associated with
-     */
-    protected abstract void resetState(@Nullable View source);
-
-    /**
-     * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource}
-     * into one, with source set to the resulting {@link View}
-     *
-     * A result of {@code null} means merger is not possible, resulting in the currently pending
-     * event being flushed before proceeding.
-     */
-    protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
-        return null;
-    }
-
-    /**
-     * Schedules a {@link #performSendEvent} with the source {@link View} set to given
-     * {@code source}
-     *
-     * If an event is already scheduled a {@link #tryMerge merge} will be attempted.
-     * If merging is not possible (as indicated by the null result from {@link #tryMerge}),
-     * the currently scheduled event will be {@link #sendNow sent immediately} and the new one
-     * will be scheduled afterwards.
-     */
-    protected final void scheduleFor(@NonNull View source) {
-        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")");
-
-        Handler uiHandler = source.getHandler();
-        if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) {
-            CalledFromWrongThreadException e = new CalledFromWrongThreadException(
-                    "Expected to be called from main thread but was called from "
-                            + Thread.currentThread());
-            // TODO: Throw the exception
-            Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android "
-                    + "versions will throw an exception.", e);
-        }
-
-        if (!isAccessibilityEnabled(source)) return;
-
-        if (mIsPending) {
-            View merged = tryMerge(mSource, source);
-            if (merged != null) {
-                setSource(merged);
-                return;
-            } else {
-                sendNow();
-            }
-        }
-
-        setSource(source);
-
-        final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis;
-        final long minEventIntervalMillis =
-                ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
-        if (timeSinceLastMillis >= minEventIntervalMillis) {
-            sendNow();
-        } else {
-            mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis);
-        }
-    }
-
-    static boolean isAccessibilityEnabled(@NonNull View contextProvider) {
-        return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled();
-    }
-
-    protected final void sendNow(View source) {
-        setSource(source);
-        sendNow();
-    }
-
-    private void sendNow() {
-        mSource.removeCallbacks(mWorker);
-        mWorker.run();
-    }
-
-    /**
-     * Flush the event if one is pending
-     */
-    public void sendNowIfPending() {
-        if (mIsPending) sendNow();
-    }
-
-    /**
-     * Cancel the event if one is pending and is for the given view
-     */
-    public final void cancelIfPendingFor(@NonNull View source) {
-        if (isPendingFor(source)) cancelIfPending(this);
-    }
-
-    /**
-     * @return whether an event is currently pending for the given source view
-     */
-    protected final boolean isPendingFor(@Nullable View source) {
-        return mIsPending && mSource == source;
-    }
-
-    /**
-     * Cancel the event if one is not null and pending
-     */
-    public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) {
-        if (sender == null || !sender.checkAndResetIsPending()) return;
-        sender.mSource.removeCallbacks(sender.mWorker);
-        sender.resetStateInternal();
-    }
-
-    void resetStateInternal() {
-        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()");
-
-        resetState(mSource);
-        setSource(null);
-    }
-
-    boolean checkAndResetIsPending() {
-        if (mIsPending) {
-            mIsPending = false;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void setSource(@Nullable View source) {
-        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")");
-
-        if (source == null && mIsPending) {
-            Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this);
-            return;
-        }
-
-        if (source != null && !mIsPending) {
-            // At most one can be pending at any given time
-            View oldSource = mSource;
-            if (oldSource != null) {
-                ViewRootImpl viewRootImpl = oldSource.getViewRootImpl();
-                if (viewRootImpl != null) {
-                    viewRootImpl.flushPendingAccessibilityEvents();
-                }
-            }
-            mIsPending = true;
-        }
-        mSource = source;
-    }
-
-    String thisClass() {
-        return getClass().getSimpleName();
-    }
-
-    @Override
-    public String toString() {
-        return thisClass() + "(" + mSource + ")";
-    }
-
-}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 594d240..a13de75 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6864,7 +6864,7 @@
             // detached and we do not allow detached views to fire accessibility
             // events. So we are announcing that the subtree changed giving a chance
             // to clients holding on to a view in this subtree to refresh it.
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
 
             // Don't scrap views that have transient state.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 08374cb..6c19256 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -1093,7 +1093,7 @@
             checkSelectionChanged();
         }
 
-        notifyAccessibilitySubtreeChanged();
+        notifySubtreeAccessibilityStateChangedIfNeeded();
     }
 
     /**
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index af01a3e..92bfd56 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -132,7 +132,7 @@
         if (mChecked != checked) {
             mChecked = checked;
             refreshDrawableState();
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index e57f153..0762b15 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -158,7 +158,7 @@
             mCheckedFromResource = false;
             mChecked = checked;
             refreshDrawableState();
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
 
             // Avoid infinite recursions if setChecked() is called from a listener
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7d3fcf4..5710db3 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2397,7 +2397,7 @@
         setText(mText);
 
         if (hasPasswordTransformationMethod()) {
-            notifyAccessibilityStateChanged(
+            notifyViewAccessibilityStateChangedIfNeeded(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
 
@@ -5152,7 +5152,8 @@
     public void setAccessibilityHeading(boolean isHeading) {
         if (isHeading != mIsAccessibilityHeading) {
             mIsAccessibilityHeading = isHeading;
-            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -5659,7 +5660,7 @@
         sendOnTextChanged(text, 0, oldlen, textLength);
         onTextChanged(text, 0, oldlen, textLength);
 
-        notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
+        notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
 
         if (needEditableForNotification) {
             sendAfterTextChanged((Editable) text);
@@ -6393,7 +6394,7 @@
     public void setError(CharSequence error, Drawable icon) {
         createEditorIfNeeded();
         mEditor.setError(error, icon);
-        notifyAccessibilityStateChanged(
+        notifyViewAccessibilityStateChangedIfNeeded(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index 379602a..59e5a64 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -29,9 +29,6 @@
         return a != null ? a : Preconditions.checkNotNull(b);
     }
 
-    /**
-     * Compares two {@link Nullable} objects with {@code null} values considered the smallest
-     */
     public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) {
         if (a != null) {
             return (b != null) ? a.compareTo(b) : 1;
@@ -39,13 +36,4 @@
             return (b != null) ? -1 : 0;
         }
     }
-
-    /**
-     * @return {@code null} if the given instance is not of the given calss, or the given
-     *         instance otherwise
-     */
-    @Nullable
-    public static <S, T extends S> T castOrNull(@Nullable S instance, @NonNull Class<T> c) {
-        return c.isInstance(instance) ? (T) instance : null;
-    }
 }
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 6f2246a..7635a72 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -22,7 +22,9 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -502,7 +504,7 @@
     }
 
     private void onCollapsedChanged(boolean isCollapsed) {
-        notifyAccessibilityStateChanged(
+        notifyViewAccessibilityStateChangedIfNeeded(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
 
         if (mScrollIndicatorDrawable != null) {