diff options
12 files changed, 706 insertions, 10 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 853528f908bb..12ffdb37f106 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -65,6 +65,7 @@ import android.app.servertransaction.PendingTransactionActions.StopInfo; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.TransactionExecutor; import android.app.servertransaction.TransactionExecutorHelper; +import android.app.servertransaction.WindowTokenClientController; import android.bluetooth.BluetoothFrameworkInitializer; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; @@ -6221,6 +6222,18 @@ public final class ActivityThread extends ClientTransactionHandler false /* clearPending */); } + @Override + public void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId) { + WindowTokenClientController.getInstance().onWindowContextConfigurationChanged(clientToken, + configuration, displayId); + } + + @Override + public void handleWindowContextWindowRemoval(@NonNull IBinder clientToken) { + WindowTokenClientController.getInstance().onWindowContextWindowRemoved(clientToken); + } + /** * Sends windowing mode change callbacks to {@link Activity} if applicable. * diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 49fb794a0a25..f7a43f42f2ef 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -163,6 +163,13 @@ public abstract class ClientTransactionHandler { public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r, Configuration overrideConfig, int displayId); + /** Deliver {@link android.window.WindowContext} configuration change. */ + public abstract void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId); + + /** Deliver {@link android.window.WindowContext} window removal event. */ + public abstract void handleWindowContextWindowRemoval(@NonNull IBinder clientToken); + /** Deliver result from another activity. */ public abstract void handleSendResult( @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason); diff --git a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java new file mode 100644 index 000000000000..3ac642fd4664 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java @@ -0,0 +1,135 @@ +/* + * 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.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; + +import java.util.Objects; + +/** + * {@link android.window.WindowContext} configuration change message. + * @hide + */ +public class WindowContextConfigurationChangeItem extends ClientTransactionItem { + + @Nullable + private IBinder mClientToken; + @Nullable + private Configuration mConfiguration; + private int mDisplayId; + + @Override + public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + @NonNull PendingTransactionActions pendingActions) { + client.handleWindowContextConfigurationChanged(mClientToken, mConfiguration, mDisplayId); + } + + // ObjectPoolItem implementation + + private WindowContextConfigurationChangeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowContextConfigurationChangeItem obtain( + @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) { + WindowContextConfigurationChangeItem instance = + ObjectPool.obtain(WindowContextConfigurationChangeItem.class); + if (instance == null) { + instance = new WindowContextConfigurationChangeItem(); + } + instance.mClientToken = requireNonNull(clientToken); + instance.mConfiguration = requireNonNull(config); + instance.mDisplayId = displayId; + + return instance; + } + + @Override + public void recycle() { + mClientToken = null; + mConfiguration = null; + mDisplayId = INVALID_DISPLAY; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mClientToken); + dest.writeTypedObject(mConfiguration, flags); + dest.writeInt(mDisplayId); + } + + /** Reads from Parcel. */ + private WindowContextConfigurationChangeItem(@NonNull Parcel in) { + mClientToken = in.readStrongBinder(); + mConfiguration = in.readTypedObject(Configuration.CREATOR); + mDisplayId = in.readInt(); + } + + public static final @NonNull Creator<WindowContextConfigurationChangeItem> CREATOR = + new Creator<>() { + public WindowContextConfigurationChangeItem createFromParcel(Parcel in) { + return new WindowContextConfigurationChangeItem(in); + } + + public WindowContextConfigurationChangeItem[] newArray(int size) { + return new WindowContextConfigurationChangeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowContextConfigurationChangeItem other = (WindowContextConfigurationChangeItem) o; + return Objects.equals(mClientToken, other.mClientToken) + && Objects.equals(mConfiguration, other.mConfiguration) + && mDisplayId == other.mDisplayId; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mClientToken); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + mDisplayId; + return result; + } + + @Override + public String toString() { + return "WindowContextConfigurationChangeItem{clientToken=" + mClientToken + + ", config=" + mConfiguration + + ", displayId=" + mDisplayId + + "}"; + } +} diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java new file mode 100644 index 000000000000..ed52a6496e95 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java @@ -0,0 +1,112 @@ +/* + * 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 java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; + +import java.util.Objects; + +/** + * {@link android.window.WindowContext} window removal message. + * @hide + */ +public class WindowContextWindowRemovalItem extends ClientTransactionItem { + + @Nullable + private IBinder mClientToken; + + @Override + public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token, + @NonNull PendingTransactionActions pendingActions) { + client.handleWindowContextWindowRemoval(mClientToken); + } + + // ObjectPoolItem implementation + + private WindowContextWindowRemovalItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowContextWindowRemovalItem obtain(@NonNull IBinder clientToken) { + WindowContextWindowRemovalItem instance = + ObjectPool.obtain(WindowContextWindowRemovalItem.class); + if (instance == null) { + instance = new WindowContextWindowRemovalItem(); + } + instance.mClientToken = requireNonNull(clientToken); + + return instance; + } + + @Override + public void recycle() { + mClientToken = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mClientToken); + } + + /** Reads from Parcel. */ + private WindowContextWindowRemovalItem(@NonNull Parcel in) { + mClientToken = in.readStrongBinder(); + } + + public static final @NonNull Creator<WindowContextWindowRemovalItem> CREATOR = new Creator<>() { + public WindowContextWindowRemovalItem createFromParcel(Parcel in) { + return new WindowContextWindowRemovalItem(in); + } + + public WindowContextWindowRemovalItem[] newArray(int size) { + return new WindowContextWindowRemovalItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowContextWindowRemovalItem other = (WindowContextWindowRemovalItem) o; + return Objects.equals(mClientToken, other.mClientToken); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mClientToken); + return result; + } + + @Override + public String toString() { + return "WindowContextWindowRemovalItem{clientToken=" + mClientToken + "}"; + } +} diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java index 28e2040de9d5..5d123a043835 100644 --- a/core/java/android/app/servertransaction/WindowTokenClientController.java +++ b/core/java/android/app/servertransaction/WindowTokenClientController.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.Log; import android.view.IWindowManager; import android.window.WindowContext; import android.window.WindowTokenClient; @@ -41,6 +42,7 @@ import com.android.internal.annotations.VisibleForTesting; */ public class WindowTokenClientController { + private static final String TAG = WindowTokenClientController.class.getSimpleName(); private static WindowTokenClientController sController; private final Object mLock = new Object(); @@ -61,7 +63,7 @@ public class WindowTokenClientController { /** Overrides the {@link #getInstance()} for test only. */ @VisibleForTesting - public static void overrideInstance(@NonNull WindowTokenClientController controller) { + public static void overrideForTesting(@NonNull WindowTokenClientController controller) { synchronized (WindowTokenClientController.class) { sController = controller; } @@ -90,7 +92,7 @@ public class WindowTokenClientController { if (configuration == null) { return false; } - onWindowContainerTokenAttached(client, displayId, configuration); + onWindowContextTokenAttached(client, displayId, configuration); return true; } @@ -116,7 +118,7 @@ public class WindowTokenClientController { if (configuration == null) { return false; } - onWindowContainerTokenAttached(client, displayId, configuration); + onWindowContextTokenAttached(client, displayId, configuration); return true; } @@ -153,7 +155,7 @@ public class WindowTokenClientController { } } - private void onWindowContainerTokenAttached(@NonNull WindowTokenClient client, int displayId, + private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, int displayId, @NonNull Configuration configuration) { synchronized (mLock) { mWindowTokenClientMap.put(client.asBinder(), client); @@ -161,4 +163,33 @@ public class WindowTokenClientController { client.onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); } + + /** Called when receives {@link WindowContextConfigurationChangeItem}. */ + public void onWindowContextConfigurationChanged(@NonNull IBinder clientToken, + @NonNull Configuration configuration, int displayId) { + final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); + if (windowTokenClient != null) { + windowTokenClient.onConfigurationChanged(configuration, displayId); + } + } + + /** Called when receives {@link WindowContextWindowRemovalItem}. */ + public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) { + final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); + if (windowTokenClient != null) { + windowTokenClient.onWindowTokenRemoved(); + } + } + + @Nullable + private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) { + final WindowTokenClient windowTokenClient; + synchronized (mLock) { + windowTokenClient = mWindowTokenClientMap.get(clientToken); + } + if (windowTokenClient == null) { + Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken); + } + return windowTokenClient; + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 99a4f6b41ef3..6aa85062562c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -34,6 +34,7 @@ import android.util.ArraySet; import android.util.Log; import android.view.inputmethod.InputMethodManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -184,6 +185,15 @@ public final class WindowManagerGlobal { } } + /** Overrides the {@link #getWindowManagerService()} for test only. */ + @VisibleForTesting + public static void overrideWindowManagerServiceForTesting( + @NonNull IWindowManager windowManager) { + synchronized (WindowManagerGlobal.class) { + sWindowManagerService = windowManager; + } + } + @UnsupportedAppUsage public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 55b823be3cb8..47d3df870216 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -20,7 +20,6 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import android.annotation.AnyThread; -import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.NonNull; import android.app.ActivityThread; @@ -97,9 +96,10 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param newConfig the updated {@link Configuration} * @param newDisplayId the updated {@link android.view.Display} ID */ - @BinderThread + @AnyThread @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { + // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); } @@ -188,9 +188,10 @@ public class WindowTokenClient extends IWindowToken.Stub { } } - @BinderThread + @AnyThread @Override public void onWindowTokenRemoved() { + // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction mHandler.post(PooledLambda.obtainRunnable( WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse()); } diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 48577416b3d0..c1b55cdfc6d7 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +30,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.app.Activity; @@ -46,6 +49,7 @@ import android.app.servertransaction.ConfigurationChangeItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.content.Intent; import android.content.res.CompatibilityInfo; @@ -70,6 +74,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.content.ReferrerIntent; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -83,7 +88,7 @@ import java.util.function.Consumer; /** * Test for verifying {@link android.app.ActivityThread} class. * Build/Install/Run: - * atest FrameworksCoreTests:android.app.activity.ActivityThreadTest + * atest FrameworksCoreTests:ActivityThreadTest */ @RunWith(AndroidJUnit4.class) @MediumTest @@ -100,14 +105,24 @@ public class ActivityThreadTest { new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); + private WindowTokenClientController mOriginalWindowTokenClientController; + private ArrayList<VirtualDisplay> mCreatedVirtualDisplays; + @Before + public void setup() { + // Keep track of the original controller, so that it can be used to restore in tearDown() + // when there is override in some test cases. + mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); + } + @After public void tearDown() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } + WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); } @Test @@ -730,6 +745,39 @@ public class ActivityThreadTest { assertFalse(activity.enterPipSkipped()); } + @Test + public void testHandleWindowContextConfigurationChanged() { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final ActivityThread activityThread = activity.getActivityThread(); + final WindowTokenClientController windowTokenClientController = + mock(WindowTokenClientController.class); + WindowTokenClientController.overrideForTesting(windowTokenClientController); + final IBinder clientToken = mock(IBinder.class); + final Configuration configuration = new Configuration(); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread + .handleWindowContextConfigurationChanged( + clientToken, configuration, DEFAULT_DISPLAY)); + + verify(windowTokenClientController).onWindowContextConfigurationChanged( + clientToken, configuration, DEFAULT_DISPLAY); + } + + @Test + public void testHandleWindowContextWindowRemoval() { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final ActivityThread activityThread = activity.getActivityThread(); + final WindowTokenClientController windowTokenClientController = + mock(WindowTokenClientController.class); + WindowTokenClientController.overrideForTesting(windowTokenClientController); + final IBinder clientToken = mock(IBinder.class); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread + .handleWindowContextWindowRemoval(clientToken)); + + verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken); + } + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int)} to try to push activity configuration to the activity for the given diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java new file mode 100644 index 000000000000..7811e1a58c22 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java @@ -0,0 +1,65 @@ +/* + * 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.Display.DEFAULT_DISPLAY; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.content.res.Configuration; +import android.os.IBinder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowContextConfigurationChangeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowContextConfigurationChangeItemTest + */ +public class WindowContextConfigurationChangeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private IBinder mToken; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mClientToken; + // Can't mock final class. + private final Configuration mConfiguration = new Configuration(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() { + final WindowContextConfigurationChangeItem item = WindowContextConfigurationChangeItem + .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY); + item.execute(mHandler, mToken, mPendingActions); + + verify(mHandler).handleWindowContextConfigurationChanged(mClientToken, mConfiguration, + DEFAULT_DISPLAY); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java new file mode 100644 index 000000000000..2c83c70e9ae2 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java @@ -0,0 +1,59 @@ +/* + * 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 org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowContextWindowRemovalItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowContextWindowRemovalItemTest + */ +public class WindowContextWindowRemovalItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private IBinder mToken; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mClientToken; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() { + final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain( + mClientToken); + item.execute(mHandler, mToken, mPendingActions); + + verify(mHandler).handleWindowContextWindowRemoval(mClientToken); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java new file mode 100644 index 000000000000..3b2fe583f605 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowTokenClientControllerTest.java @@ -0,0 +1,215 @@ +/* + * 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.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; +import android.window.WindowTokenClient; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowTokenClientController}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowTokenClientControllerTest + */ +@SmallTest +@Presubmit +public class WindowTokenClientControllerTest { + + @Mock + private IWindowManager mWindowManagerService; + @Mock + private WindowTokenClient mWindowTokenClient; + @Mock + private IBinder mClientToken; + @Mock + private IBinder mWindowToken; + // Can't mock final class. + private final Configuration mConfiguration = new Configuration(); + + private IWindowManager mOriginalWindowManagerService; + + private WindowTokenClientController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService(); + WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService); + doReturn(mClientToken).when(mWindowTokenClient).asBinder(); + mController = spy(WindowTokenClientController.getInstance()); + } + + @After + public void tearDown() { + WindowManagerGlobal.overrideWindowManagerServiceForTesting(mOriginalWindowManagerService); + } + + @Test + public void testAttachToDisplayArea() throws RemoteException { + doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + + assertFalse(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */)); + verify(mWindowManagerService).attachWindowContextToDisplayArea(mWindowTokenClient, + TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */); + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean()); + + doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + + assertTrue(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */)); + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY, + false /* shouldReportConfigChange */); + } + + @Test + public void testAttachToDisplayArea_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(null).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea( + any(), anyInt(), anyInt(), any()); + mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY, + DEFAULT_DISPLAY, null /* options */); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testAttachToDisplayContent() throws RemoteException { + doReturn(null).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + + assertFalse(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY)); + verify(mWindowManagerService).attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean()); + + doReturn(mConfiguration).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + + assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY)); + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY, + false /* shouldReportConfigChange */); + } + + @Test + public void testAttachToDisplayContent_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(null).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + doReturn(mConfiguration).when(mWindowManagerService).attachToDisplayContent( + any(), anyInt()); + mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testAttachToWindowToken() throws RemoteException { + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + verify(mWindowManagerService).attachWindowContextToWindowToken(mWindowTokenClient, + mWindowToken); + } + + @Test + public void testAttachToWindowToken_detachIfNeeded() throws RemoteException { + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService, never()).detachWindowContextFromWindowContainer(any()); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + mController.detachIfNeeded(mWindowTokenClient); + + verify(mWindowManagerService).detachWindowContextFromWindowContainer(any()); + } + + @Test + public void testOnWindowContextConfigurationChanged() { + mController.onWindowContextConfigurationChanged( + mClientToken, mConfiguration, DEFAULT_DISPLAY); + + verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt()); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + mController.onWindowContextConfigurationChanged( + mClientToken, mConfiguration, DEFAULT_DISPLAY); + + verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY); + } + + @Test + public void testOnWindowContextWindowRemoved() { + mController.onWindowContextWindowRemoved(mClientToken); + + verify(mWindowTokenClient, never()).onWindowTokenRemoved(); + + mController.attachToWindowToken(mWindowTokenClient, mWindowToken); + + mController.onWindowContextWindowRemoved(mClientToken); + + verify(mWindowTokenClient).onWindowTokenRemoved(); + } +} diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index 813c360b29e4..5f2aecc40d16 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -71,14 +71,14 @@ public class WindowContextControllerTest { mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); mOriginalController = WindowTokenClientController.getInstance(); - WindowTokenClientController.overrideInstance(mWindowTokenClientController); + WindowTokenClientController.overrideForTesting(mWindowTokenClientController); doReturn(true).when(mWindowTokenClientController).attachToDisplayArea( eq(mMockToken), anyInt(), anyInt(), any()); } @After public void tearDown() { - WindowTokenClientController.overrideInstance(mOriginalController); + WindowTokenClientController.overrideForTesting(mOriginalController); } @Test(expected = IllegalStateException.class) |