Throttle content changed events per source

Previous attempt Ic18cd4aa310ab37e40c6623625b59b4b9ebfea2f caused a
perormance regression.
To mitigate, this allows event throttling when the source node is the
same as the previous event.

Bug: 277305460
Bug: 280057969
Test: CtsAccessibilityTestCases CtsAccessibilityServiceTestCases
Test: android.inputmethod.ImePerfTest#testShowImeCold
Change-Id: Ieb506ca2ee8aee7a529183507e04382d167304b3
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 17627e9..402d5ea 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -815,7 +815,6 @@
     final HighContrastTextManager mHighContrastTextManager;
 
     SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
-    boolean mSendingAccessibilityWindowContentChange = false;
 
     HashSet<View> mTempHashSet;
 
@@ -9875,29 +9874,7 @@
 
     @Override
     public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
-        Objects.requireNonNull(source);
-        if (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) {
-            postSendWindowContentChangedCallback(source, changeType);
-            return;
-        }
-        if (mSendingAccessibilityWindowContentChange) {
-            // This allows re-entering this method.
-            // Some apps update views during an event dispatch, which triggers another a11y event.
-            // In order to avoid an infinite loop, postpone second event dispatch.
-            mHandler.post(() -> notifySubtreeAccessibilityStateChanged(child, source, changeType));
-            return;
-        }
-
-        AccessibilityEvent event =
-                new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        event.setContentChangeTypes(changeType);
-        event.setAction(mAccessibilityManager.getPerformingAction());
-        mSendingAccessibilityWindowContentChange = true;
-        try {
-            source.sendAccessibilityEventUnchecked(event);
-        } finally {
-            mSendingAccessibilityWindowContentChange = false;
-        }
+        postSendWindowContentChangedCallback(Objects.requireNonNull(source), changeType);
     }
 
     @Override
@@ -10953,6 +10930,11 @@
                     run();
                 }
             }
+
+            if (!canContinueThrottle(source, changeType)) {
+                removeCallbacksAndRun();
+            }
+
             if (mSource != null) {
                 // If there is no common predecessor, then mSource points to
                 // a removed view, hence in this case always prefer the source.
@@ -10987,12 +10969,12 @@
                 mOrigin = Thread.currentThread().getStackTrace();
             }
             final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
-            final long minEventIntevalMillis =
+            final long minEventIntervalMillis =
                     ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
-            if (timeSinceLastMillis >= minEventIntevalMillis) {
+            if (timeSinceLastMillis >= minEventIntervalMillis) {
                 removeCallbacksAndRun();
             } else {
-                mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
+                mHandler.postDelayed(this, minEventIntervalMillis - timeSinceLastMillis);
             }
         }
 
@@ -11000,6 +10982,20 @@
             mHandler.removeCallbacks(this);
             run();
         }
+
+        private boolean canContinueThrottle(View source, int changeType) {
+            if (mSource == null) {
+                // We don't have a pending event.
+                return true;
+            }
+            if (mSource == source) {
+                // We can merge a new event with a pending event from the same source.
+                return true;
+            }
+            // We can merge subtree change events.
+            return changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+                    && mChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+        }
     }
 
     private static class UnhandledKeyManager {