Merge "Introduce WindowContextController" into sc-dev
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index bff2252..375f4cf 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -27,10 +27,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.IWindowManager;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -48,13 +45,11 @@
 @UiContext
 public class WindowContext extends ContextWrapper {
     private final WindowManager mWindowManager;
-    private final IWindowManager mWms;
-    private final @NonNull IBinder mToken;
     private final @WindowManager.LayoutParams.WindowType int mType;
     private final @Nullable Bundle mOptions;
-    private boolean mListenerRegistered;
     private final ComponentCallbacksController mCallbacksController =
             new ComponentCallbacksController();
+    private final WindowContextController mController;
 
     /**
      * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -69,9 +64,9 @@
 
         mType = type;
         mOptions = options;
-        mWms = WindowManagerGlobal.getWindowManagerService();
-        mToken = getWindowContextToken();
         mWindowManager = createWindowContextWindowManager(this);
+        IBinder token = getWindowContextToken();
+        mController = new WindowContextController(token);
 
         Reference.reachabilityFence(this);
     }
@@ -81,12 +76,7 @@
      * to receive configuration changes of the associated {@link WindowManager} node.
      */
     public void registerWithServer() {
-        try {
-            mListenerRegistered = mWms.registerWindowContextListener(mToken, mType, getDisplayId(),
-                    mOptions);
-        }  catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mController.registerListener(mType, getDisplayId(), mOptions);
     }
 
     @Override
@@ -106,14 +96,7 @@
     /** Used for test to invoke because we can't invoke finalize directly. */
     @VisibleForTesting
     public void release() {
-        if (mListenerRegistered) {
-            mListenerRegistered = false;
-            try {
-                mWms.unregisterWindowContextListener(mToken);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
+        mController.unregisterListenerIfNeeded();
         destroy();
     }
 
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
new file mode 100644
index 0000000..6143414
--- /dev/null
+++ b/core/java/android/window/WindowContextController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The controller to manage {@link WindowContext} listener, such as registering and unregistering
+ * the listener.
+ *
+ * @hide
+ */
+public class WindowContextController {
+    private final IWindowManager mWms;
+    @VisibleForTesting
+    public boolean mListenerRegistered;
+    @NonNull
+    private final IBinder mToken;
+
+    /**
+     * Window Context Controller constructor
+     *
+     * @param token The token to register to the window context listener. It is usually from
+     *              {@link Context#getWindowContextToken()}.
+     */
+    public WindowContextController(@NonNull IBinder token) {
+        mToken = token;
+        mWms = WindowManagerGlobal.getWindowManagerService();
+    }
+
+    /** Used for test only. DO NOT USE it in production code. */
+    @VisibleForTesting
+    public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+        mToken = token;
+        mWms = mockWms;
+    }
+
+    /**
+     * Registers the {@code mToken} to the window context listener.
+     *
+     * @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
+     */
+    public void registerListener(@WindowType int type, int displayId,  @Nullable Bundle options) {
+        if (mListenerRegistered) {
+            throw new UnsupportedOperationException("A Window Context can only register a listener"
+                    + " once.");
+        }
+        try {
+            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, displayId,
+                    options);
+        }  catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters the window context listener associated with the {@code mToken} if it has been
+     * registered.
+     */
+    public void unregisterListenerIfNeeded() {
+        if (mListenerRegistered) {
+            try {
+                mWms.unregisterWindowContextListener(mToken);
+                mListenerRegistered = false;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
new file mode 100644
index 0000000..e4fc19c
--- /dev/null
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.window;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContextController}
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowContextControllerTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextControllerTest {
+    private WindowContextController mController;
+    private IWindowManager mMockWms;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockWms = mock(IWindowManager.class);
+        mController = new WindowContextController(new Binder(), mMockWms);
+
+        doReturn(true).when(mMockWms).registerWindowContextListener(
+                any(), anyInt(), anyInt(), any());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRegisterListenerTwiceThrowException() {
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+    }
+
+    @Test
+    public void testUnregisterListenerIfNeeded_NotRegisteredYet_DoNothing() throws Exception {
+        mController.unregisterListenerIfNeeded();
+
+        verify(mMockWms, never()).registerWindowContextListener(any(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testRegisterAndUnRegisterListener() {
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+
+        assertThat(mController.mListenerRegistered).isTrue();
+
+        mController.unregisterListenerIfNeeded();
+
+        assertThat(mController.mListenerRegistered).isFalse();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 7a0be97..bcd6ed7 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -61,7 +61,8 @@
             "android.view.PendingInsetsControllerTest",
             "android.window.WindowContextTest",
             "android.window.WindowMetricsHelperTest",
-            "android.app.activity.ActivityThreadTest"
+            "android.app.activity.ActivityThreadTest",
+            "android.window.WindowContextControllerTest"
     };
 
     public FrameworksTestsFilter(Bundle testArgs) {