Merge "Revert "Migrate WindowContext#onConfigurationChanged to ClientTransaction (1/n)"" into main
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1f95497..59b0dac 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -27,7 +27,6 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UiContext;
-import android.app.servertransaction.WindowTokenClientController;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
@@ -3277,8 +3276,7 @@
         // if this Context is not a WindowContext. WindowContext finalization is handled in
         // WindowContext class.
         if (mToken instanceof WindowTokenClient && mOwnsToken) {
-            WindowTokenClientController.getInstance().detachIfNeeded(
-                    (WindowTokenClient) mToken);
+            ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
         }
         super.finalize();
     }
@@ -3306,7 +3304,7 @@
         final WindowTokenClient token = new WindowTokenClient();
         final ContextImpl context = systemContext.createWindowContextBase(token, displayId);
         token.attachContext(context);
-        WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId);
+        token.attachToDisplayContent(displayId);
         context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
         context.mOwnsToken = true;
 
diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java
deleted file mode 100644
index 28e2040..0000000
--- a/core/java/android/app/servertransaction/WindowTokenClientController.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2023 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.app.servertransaction;
-
-import static android.view.WindowManager.LayoutParams.WindowType;
-import static android.view.WindowManagerGlobal.getWindowManagerService;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.view.IWindowManager;
-import android.window.WindowContext;
-import android.window.WindowTokenClient;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch
- * corresponding window configuration change from server side.
- * @hide
- */
-public class WindowTokenClientController {
-
-    private static WindowTokenClientController sController;
-
-    private final Object mLock = new Object();
-
-    /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */
-    @GuardedBy("mLock")
-    private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
-
-    /** Gets the singleton controller. */
-    public static WindowTokenClientController getInstance() {
-        synchronized (WindowTokenClientController.class) {
-            if (sController == null) {
-                sController = new WindowTokenClientController();
-            }
-            return sController;
-        }
-    }
-
-    /** Overrides the {@link #getInstance()} for test only. */
-    @VisibleForTesting
-    public static void overrideInstance(@NonNull WindowTokenClientController controller) {
-        synchronized (WindowTokenClientController.class) {
-            sController = controller;
-        }
-    }
-
-    private WindowTokenClientController() {}
-
-    /**
-     * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
-     *
-     * @param client The {@link WindowTokenClient} to attach.
-     * @param type The window type of the {@link WindowContext}
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @param options The window context launched option
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
-            @WindowType int type, int displayId, @Nullable Bundle options) {
-        final Configuration configuration;
-        try {
-            configuration = getWindowManagerService()
-                    .attachWindowContextToDisplayArea(client, type, displayId, options);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        if (configuration == null) {
-            return false;
-        }
-        onWindowContainerTokenAttached(client, displayId, configuration);
-        return true;
-    }
-
-    /**
-     * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}.
-     *
-     * @param client The {@link WindowTokenClient} to attach.
-     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
-     * @return {@code true} if attaching successfully.
-     */
-    public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) {
-        final IWindowManager wms = getWindowManagerService();
-        // #createSystemUiContext may call this method before WindowManagerService is initialized.
-        if (wms == null) {
-            return false;
-        }
-        final Configuration configuration;
-        try {
-            configuration = wms.attachToDisplayContent(client, displayId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        if (configuration == null) {
-            return false;
-        }
-        onWindowContainerTokenAttached(client, displayId, configuration);
-        return true;
-    }
-
-    /**
-     * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
-     *
-     * @param client The {@link WindowTokenClient} to attach.
-     * @param windowToken the window token to associated with
-     */
-    public void attachToWindowToken(@NonNull WindowTokenClient client,
-            @NonNull IBinder windowToken) {
-        try {
-            getWindowManagerService().attachWindowContextToWindowToken(client, windowToken);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        // We don't report configuration change for now.
-        synchronized (mLock) {
-            mWindowTokenClientMap.put(client.asBinder(), client);
-        }
-    }
-
-    /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
-    public void detachIfNeeded(@NonNull WindowTokenClient client) {
-        synchronized (mLock) {
-            if (mWindowTokenClientMap.remove(client.asBinder()) == null) {
-                return;
-            }
-        }
-        try {
-            getWindowManagerService().detachWindowContextFromWindowContainer(client);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private void onWindowContainerTokenAttached(@NonNull WindowTokenClient client, int displayId,
-            @NonNull Configuration configuration) {
-        synchronized (mLock) {
-            mWindowTokenClientMap.put(client.asBinder(), client);
-        }
-        client.onConfigurationChanged(configuration, displayId,
-                false /* shouldReportConfigChange */);
-    }
-}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index eb270e2..4b9a957 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -21,7 +21,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.servertransaction.WindowTokenClientController;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -105,8 +104,7 @@
             throw new IllegalStateException("A Window Context can be only attached to "
                     + "a DisplayArea once.");
         }
-        mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea(
-                mToken, type, displayId, options)
+        mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options)
                 ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
         if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
             Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:"
@@ -142,13 +140,13 @@
             throw new IllegalStateException("The Window Context should have been attached"
                     + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
         }
-        WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken);
+        mToken.attachToWindowToken(windowToken);
     }
 
     /** Detaches the window context from the node it's currently associated with. */
     public void detachIfNeeded() {
         if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
-            WindowTokenClientController.getInstance().detachIfNeeded(mToken);
+            mToken.detachFromWindowContainerIfNeeded();
             mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
             if (DEBUG_ATTACH) {
                 Log.d(TAG, "Detach Window Context.");
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 55b823b..a208634 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -23,10 +23,10 @@
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
-import android.app.servertransaction.WindowTokenClientController;
 import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -36,9 +36,14 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.lang.ref.WeakReference;
@@ -65,11 +70,15 @@
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
+    private IWindowManager mWms;
+
     @GuardedBy("itself")
     private final Configuration mConfiguration = new Configuration();
 
     private boolean mShouldDumpConfigForIme;
 
+    private boolean mAttachToWindowContainer;
+
     private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
 
     /**
@@ -92,6 +101,88 @@
     }
 
     /**
+     * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
+     *
+     * @param type The window type of the {@link WindowContext}
+     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+     * @param options The window context launched option
+     * @return {@code true} if attaching successfully.
+     */
+    public boolean attachToDisplayArea(@WindowType int type, int displayId,
+            @Nullable Bundle options) {
+        try {
+            final Configuration configuration = getWindowManagerService()
+                    .attachWindowContextToDisplayArea(this, type, displayId, options);
+            if (configuration == null) {
+                return false;
+            }
+            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+            mAttachToWindowContainer = true;
+            return true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
+     *
+     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+     * @return {@code true} if attaching successfully.
+     */
+    public boolean attachToDisplayContent(int displayId) {
+        final IWindowManager wms = getWindowManagerService();
+        // #createSystemUiContext may call this method before WindowManagerService is initialized.
+        if (wms == null) {
+            return false;
+        }
+        try {
+            final Configuration configuration = wms.attachToDisplayContent(this, displayId);
+            if (configuration == null) {
+                return false;
+            }
+            onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+            mAttachToWindowContainer = true;
+            return true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
+     *
+     * @param windowToken the window token to associated with
+     */
+    public void attachToWindowToken(IBinder windowToken) {
+        try {
+            getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
+            mAttachToWindowContainer = true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
+    public void detachFromWindowContainerIfNeeded() {
+        if (!mAttachToWindowContainer) {
+            return;
+        }
+        try {
+            getWindowManagerService().detachWindowContextFromWindowContainer(this);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private IWindowManager getWindowManagerService() {
+        if (mWms == null) {
+            mWms = WindowManagerGlobal.getWindowManagerService();
+        }
+        return mWms;
+    }
+
+    /**
      * Called when {@link Configuration} updates from the server side receive.
      *
      * @param newConfig the updated {@link Configuration}
@@ -116,14 +207,15 @@
      * {@code shouldReportConfigChange} is {@code true}, which is usually from
      * {@link IWindowToken#onConfigurationChanged(Configuration, int)}
      * directly, while this method could be run on any thread if it is used to initialize
-     * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
-     * or {@link WindowTokenClientController#attachToDisplayContent}.
+     * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)}
+     * or {@link #attachToDisplayContent(int)}.
      *
      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
      *                                 should be dispatched to listeners.
      *
      */
     @AnyThread
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
             boolean shouldReportConfigChange) {
         final Context context = mContextRef.get();
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 467d555..a52d2e8 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -24,13 +24,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.app.servertransaction.WindowTokenClientController;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 
@@ -58,8 +56,6 @@
 public class WindowContextControllerTest {
     private WindowContextController mController;
     @Mock
-    private WindowTokenClientController mWindowTokenClientController;
-    @Mock
     private WindowTokenClient mMockToken;
 
     @Before
@@ -67,9 +63,7 @@
         MockitoAnnotations.initMocks(this);
         mController = new WindowContextController(mMockToken);
         doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
-        WindowTokenClientController.overrideInstance(mWindowTokenClientController);
-        doReturn(true).when(mWindowTokenClientController).attachToDisplayArea(
-                eq(mMockToken), anyInt(), anyInt(), any());
+        doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any());
     }
 
     @Test(expected = IllegalStateException.class)
@@ -84,7 +78,7 @@
     public void testDetachIfNeeded_NotAttachedYet_DoNothing() {
         mController.detachIfNeeded();
 
-        verify(mWindowTokenClientController, never()).detachIfNeeded(any());
+        verify(mMockToken, never()).detachFromWindowContainerIfNeeded();
     }
 
     @Test