Fix ConcurrentModificationException in WindowContext

When shouldReportConfigChange is false, it is in process of initializing
WindowContext, which should not report config change nor trigger
onDisplayChanged.

Fix: 334285008
Test: atest FrameworksCoreTests:ClientTransactionListenerControllerTest
Change-Id: Ib66d688a4caf747db4e2f6c7097290e995918f0b
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 722d5f0..c9b4aa1 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,6 +23,8 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityThread;
@@ -94,6 +96,7 @@
      * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
      * {@link ActivityWindowInfo}.
      */
+    @AnyThread
     public void registerActivityWindowInfoChangedListener(
             @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
         if (!activityWindowInfoFlag()) {
@@ -108,6 +111,7 @@
      * Unregisters the listener that was previously registered via
      * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
      */
+    @AnyThread
     public void unregisterActivityWindowInfoChangedListener(
             @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
         if (!activityWindowInfoFlag()) {
@@ -122,6 +126,7 @@
      * Called when receives a {@link ClientTransaction} that is updating an activity's
      * {@link ActivityWindowInfo}.
      */
+    @MainThread
     public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
             @NonNull ActivityWindowInfo activityWindowInfo) {
         if (!activityWindowInfoFlag()) {
@@ -141,17 +146,20 @@
     }
 
     /** Called when starts executing a remote {@link ClientTransaction}. */
+    @MainThread
     public void onClientTransactionStarted() {
         mIsClientTransactionExecuting = true;
     }
 
     /** Called when finishes executing a remote {@link ClientTransaction}. */
+    @MainThread
     public void onClientTransactionFinished() {
         notifyDisplayManagerIfNeeded();
         mIsClientTransactionExecuting = false;
     }
 
     /** Called before updating the Configuration of the given {@code context}. */
+    @MainThread
     public void onContextConfigurationPreChanged(@NonNull Context context) {
         if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
             // Not enable for system server.
@@ -166,6 +174,7 @@
     }
 
     /** Called after updating the Configuration of the given {@code context}. */
+    @MainThread
     public void onContextConfigurationPostChanged(@NonNull Context context) {
         if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
             // Not enable for system server.
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a868d48..1ffb4ff 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -144,17 +144,26 @@
         if (context == null) {
             return;
         }
-        final ClientTransactionListenerController controller =
-                ClientTransactionListenerController.getInstance();
-        controller.onContextConfigurationPreChanged(context);
-        try {
+        if (shouldReportConfigChange) {
+            // Only report to ClientTransactionListenerController when shouldReportConfigChange,
+            // which is on the MainThread.
+            final ClientTransactionListenerController controller =
+                    getClientTransactionListenerController();
+            controller.onContextConfigurationPreChanged(context);
+            try {
+                onConfigurationChangedInner(context, newConfig, newDisplayId,
+                        shouldReportConfigChange);
+            } finally {
+                controller.onContextConfigurationPostChanged(context);
+            }
+        } else {
             onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
-        } finally {
-            controller.onContextConfigurationPostChanged(context);
         }
     }
 
-    private void onConfigurationChangedInner(@NonNull Context context,
+    /** Handles onConfiguration changed. */
+    @VisibleForTesting
+    public void onConfigurationChangedInner(@NonNull Context context,
             @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
         CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
         final boolean displayChanged;
@@ -233,4 +242,11 @@
             mContextRef.clear();
         }
     }
+
+    /** Gets {@link ClientTransactionListenerController}. */
+    @VisibleForTesting
+    @NonNull
+    public ClientTransactionListenerController getClientTransactionListenerController() {
+        return ClientTransactionListenerController.getInstance();
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 8506905..f8c2d6a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -23,15 +23,19 @@
 import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -45,6 +49,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.DisplayInfo;
 import android.window.ActivityWindowInfo;
+import android.window.WindowTokenClient;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +60,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -180,4 +186,36 @@
 
         verify(mActivityWindowInfoListener, never()).accept(any(), any());
     }
+
+    @Test
+    public void testWindowTokenClient_onConfigurationChanged() {
+        doNothing().when(mController).onContextConfigurationPreChanged(any());
+        doNothing().when(mController).onContextConfigurationPostChanged(any());
+
+        final WindowTokenClient windowTokenClient = spy(new WindowTokenClient());
+        final Context context = mock(Context.class);
+        windowTokenClient.attachContext(context);
+
+        doReturn(mController).when(windowTokenClient).getClientTransactionListenerController();
+        doNothing().when(windowTokenClient).onConfigurationChangedInner(any(), any(), anyInt(),
+                anyBoolean());
+
+        // Not trigger when shouldReportConfigChange is false.
+        windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+                false /* shouldReportConfigChange*/);
+
+        verify(mController, never()).onContextConfigurationPreChanged(any());
+        verify(mController, never()).onContextConfigurationPostChanged(any());
+
+        // Trigger in order when shouldReportConfigChange is true.
+        clearInvocations(windowTokenClient);
+        final InOrder inOrder = inOrder(mController, windowTokenClient);
+        windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+                true /* shouldReportConfigChange*/);
+
+        inOrder.verify(mController).onContextConfigurationPreChanged(context);
+        inOrder.verify(windowTokenClient).onConfigurationChangedInner(context, mConfiguration,
+                123 /* newDisplayId */, true /* shouldReportConfigChange*/);
+        inOrder.verify(mController).onContextConfigurationPostChanged(context);
+    }
 }