Improve window content change events merging from multiple views

Since event throttled is merged into ViewRootImpl [1], even when events
from different nodes are merged, it looks like an event from a single
node.
Such an event should be marked as CHANGE_TYPE_SUBTREE.

Especially, without this change, there's a corner case where
AccessibilityCache is not able to invalidate node correctly. This case
is covered by a new CTS added together with this change.

This also updates a logic to change the event source to important parent
node, which was added in [2]. This should not be applied when there's
only a single node that is dispatching events, thus this change moves it
just before an event is actually dispatched.

[1] I1e1ecf1afc399e4103da82e6ed1779abfd3ec955
[2] I3c3c66151b6cd4773de4eadd417709e9a61a7cf2

Bug: 277305460
Test: CtsAccessibilityTestCases CtsAccessibilityServiceTestCases
Change-Id: Ic71f22ed6f6f54f49810dc83dc85dcd492dfc104
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2b99e1e..1e597ca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15015,6 +15015,7 @@
     }
 
     /** @hide */
+    @Nullable
     View getSelfOrParentImportantForA11y() {
         if (isImportantForAccessibility()) return this;
         ViewParent parent = getParentForAccessibility();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 350876c..ab9baf7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -86,6 +86,7 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -11492,6 +11493,15 @@
                 event.setContentChangeTypes(mChangeTypes);
                 if (mAction.isPresent()) event.setAction(mAction.getAsInt());
                 if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+
+                if (fixMergedContentChangeEvent()) {
+                    if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+                        final View importantParent = source.getSelfOrParentImportantForA11y();
+                        if (importantParent != null) {
+                            source = importantParent;
+                        }
+                    }
+                }
                 source.sendAccessibilityEventUnchecked(event);
             } else {
                 mLastEventTimeMillis = 0;
@@ -11526,14 +11536,29 @@
             }
 
             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);
-                if (predecessor != null) {
-                    predecessor = predecessor.getSelfOrParentImportantForA11y();
+                if (fixMergedContentChangeEvent()) {
+                    View newSource = getCommonPredecessor(mSource, source);
+                    if (newSource == null) {
+                        // If there is no common predecessor, then mSource points to
+                        // a removed view, hence in this case always prefer the source.
+                        newSource = source;
+                    }
+
+                    mChangeTypes |= changeType;
+                    if (mSource != newSource) {
+                        mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+                        mSource = newSource;
+                    }
+                } else {
+                    // 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);
+                    if (predecessor != null) {
+                        predecessor = predecessor.getSelfOrParentImportantForA11y();
+                    }
+                    mSource = (predecessor != null) ? predecessor : source;
+                    mChangeTypes |= changeType;
                 }
-                mSource = (predecessor != null) ? predecessor : source;
-                mChangeTypes |= changeType;
 
                 final int performingAction = mAccessibilityManager.getPerformingAction();
                 if (performingAction != 0) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c7355c1..efae57c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,6 +53,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "fix_merged_content_change_event"
+    description: "Fixes event type and source of content change event merged in ViewRootImpl"
+    bug: "277305460"
+}
+
+flag {
+    namespace: "accessibility"
     name: "flash_notification_system_api"
     description: "Makes flash notification APIs as system APIs for calling from mainline module"
     bug: "303131332"