From 9854a1e315b53157daf12badbad2890048e26d2d Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Fri, 4 Dec 2020 16:22:01 +0800 Subject: [PATCH 1/2] Migrate WindowContext to WindowContextListener mechanism 1. Use registerWindowContextListener instead of addWindowTokenWithOptions 2. Clean up the logic of sending the config to the client in WindowToken 3. Add a mechanism in WMS#addWindow, when a WindowContext adds a view, the server side will switch to register the WindowToken for the WindowContext. In this way, we won't add a WindowToken until adding the fist view. Also, we could apply an existing window token by overriding WindowManager.LayoutParams.token. Bug: 159767464 Bug: 153369119 Test: atest WindowContextTests WindowContextPolicyTests Test: atest WindowContextTest Test: atest WindowContextListenerControllerTests Change-Id: I2396187c445306d18101706c4521edd9cf92facf --- core/java/android/app/WindowContext.java | 32 ++-- core/java/android/content/Context.java | 54 +++++- core/tests/coretests/AndroidManifest.xml | 3 + .../src/android/app/WindowContextTest.java | 154 ++++++++++++++++-- .../com/android/server/wm/WindowToken.java | 104 +----------- 5 files changed, 198 insertions(+), 149 deletions(-) diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java index 14ed414da9d09..cbe2995f2467c 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -15,8 +15,7 @@ */ package android.app; -import static android.view.WindowManagerGlobal.ADD_OKAY; -import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS; +import static android.view.WindowManagerImpl.createWindowContextWindowManager; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,8 +27,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.view.Display; import android.view.IWindowManager; +import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.WindowManagerImpl; import com.android.internal.annotations.VisibleForTesting; @@ -46,10 +45,10 @@ import java.lang.ref.Reference; */ @UiContext public class WindowContext extends ContextWrapper { - private final WindowManagerImpl mWindowManager; + private final WindowManager mWindowManager; private final IWindowManager mWms; private final WindowTokenClient mToken; - private boolean mOwnsToken; + private boolean mListenerRegistered; /** * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to @@ -86,25 +85,14 @@ public class WindowContext extends ContextWrapper { mToken.attachContext(this); - mWindowManager = new WindowManagerImpl(this); - mWindowManager.setDefaultToken(mToken); + mWindowManager = createWindowContextWindowManager(this); - int result; try { - // Register the token with WindowManager. This will also call back with the current - // config back to the client. - result = mWms.addWindowTokenWithOptions( - mToken, type, getDisplayId(), options, getPackageName()); + mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(), + options); } catch (RemoteException e) { - mOwnsToken = false; throw e.rethrowFromSystemServer(); } - if (result == ADD_TOO_MANY_TOKENS) { - throw new UnsupportedOperationException("createWindowContext failed! Too many unused " - + "window contexts. Please see Context#createWindowContext documentation for " - + "detail."); - } - mOwnsToken = result == ADD_OKAY; Reference.reachabilityFence(this); } @@ -131,10 +119,10 @@ public class WindowContext extends ContextWrapper { /** Used for test to invoke because we can't invoke finalize directly. */ @VisibleForTesting public void release() { - if (mOwnsToken) { + if (mListenerRegistered) { + mListenerRegistered = false; try { - mWms.removeWindowToken(mToken, getDisplayId()); - mOwnsToken = false; + mWms.unregisterWindowContextListener(mToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4dc41b2d51144..90ad73b1258a0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -75,6 +75,7 @@ import android.view.Display; import android.view.DisplayAdjustments; import android.view.View; import android.view.ViewDebug; +import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; import android.view.autofill.AutofillManager.AutofillClient; @@ -6101,18 +6102,19 @@ public abstract class Context { * * // WindowManager.LayoutParams initialization * ... + * // The types used in addView and createWindowContext must match. * mParams.type = TYPE_APPLICATION_OVERLAY; * ... * - * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); * * *

- * This context's configuration and resources are adjusted to a display area where the windows - * with provided type will be added. Note that all windows associated with the same context - * will have an affinity and can only be moved together between different displays or areas on a - * display. If there is a need to add different window types, or non-associated windows, - * separate Contexts should be used. + * This context's configuration and resources are adjusted to an area of the display where + * the windows with provided type will be added. Note that all windows associated with the + * same context will have an affinity and can only be moved together between different displays + * or areas on a display. If there is a need to add different window types, or + * non-associated windows, separate Contexts should be used. *

*

* Creating a window context is an expensive operation. Misuse of this API may lead to a huge @@ -6120,7 +6122,43 @@ public abstract class Context { * An approach is to create one window context with specific window type and display and * use it everywhere it's needed. *

+ *

+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to receive + * configuration changes for existing token by overriding the + * {@link android.view.WindowManager.LayoutParams#token token} of the + * {@link android.view.WindowManager.LayoutParams} passed in + * {@link WindowManager#addView(View, LayoutParams)}. This is useful when an application needs + * to attach its window to an existing activity for window token sharing use-case. + *

+ *

+ * Note that the window context in {@link Build.VERSION_CODES#R} didn't have this + * capability. This is a no-op for the window context in {@link Build.VERSION_CODES#R}. + *

+ * Below is sample code to attach an existing token to a window context: + *
+     * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
+     * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+     * final Context windowContext = anyContext.createWindowContext(primaryDisplay,
+     *         TYPE_APPLICATION, null);
      *
+     * // Get an existing token.
+     * final IBinder existingToken = activity.getWindow().getAttributes().token;
+     *
+     * // The types used in addView() and createWindowContext() must match.
+     * final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION);
+     * params.token = existingToken;
+     *
+     * // After WindowManager#addView(), the server side will extract the provided token from
+     * // LayoutParams#token (existingToken in the sample code), and switch to propagate
+     * // configuration changes from the node associated with the provided token.
+     * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+     * 
+ *

+ * Note that using {@link android.app.Application} or {@link android.app.Service} context for + * UI-related queries may result in layout or continuity issues on devices with variable screen + * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect + * the {@link Configuration} changes for the visual container. + *

* @param type Window type in {@link WindowManager.LayoutParams} * @param options A bundle used to pass window-related options * @return A {@link Context} that can be used to create @@ -6132,9 +6170,7 @@ public abstract class Context { * @see #LAYOUT_INFLATER_SERVICE * @see #WALLPAPER_SERVICE * @throws UnsupportedOperationException if this {@link Context} does not attach to a display, - * such as {@link android.app.Application Application} or {@link android.app.Service Service}, - * or the current number of window contexts without adding any view by - * {@link WindowManager#addView} exceeds five. + * such as {@link android.app.Application Application} or {@link android.app.Service Service}. */ @UiContext @NonNull diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 151a320494b46..63401a2b57268 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -148,6 +148,9 @@ + + + diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java index dcf5e025f82b1..da7304efbd3dd 100644 --- a/core/tests/coretests/src/android/app/WindowContextTest.java +++ b/core/tests/coretests/src/android/app/WindowContextTest.java @@ -18,29 +18,38 @@ package android.app; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.content.Intent; import android.hardware.display.DisplayManager; +import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.IWindowManager; import android.view.View; import android.view.WindowManager; +import android.view.WindowManager.LayoutParams.WindowType; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Tests for {@link WindowContext} * @@ -54,20 +63,14 @@ import org.junit.runner.RunWith; @SmallTest @Presubmit public class WindowContextTest { + @Rule + public ActivityTestRule mActivityRule = + new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */, + false /* launchActivity */); + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); private final WindowContext mWindowContext = createWindowContext(); - - @Test - public void testWindowContextRelease_doRemoveWindowToken() throws Throwable { - final IBinder token = mWindowContext.getWindowContextToken(); - final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); - - assertTrue("Token must be registered to WMS", wms.isWindowToken(token)); - - mWindowContext.release(); - - assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token)); - } + private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService(); @Test public void testCreateWindowContextWindowManagerAttachClientToken() { @@ -83,12 +86,133 @@ public class WindowContextTest { assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken); } + /** + * Test the {@link WindowContext} life cycle behavior to add a new window token: + *
    + *
  • The window token is created before adding the first view.
  • + *
  • The window token is registered after adding the first view.
  • + *
  • The window token is removed after {@link WindowContext}'s release.
  • + *
+ */ + @Test + public void testCreateWindowContextNewTokenFromClient() throws Throwable { + final IBinder token = mWindowContext.getWindowContextToken(); + + // Test that the window token is not created yet. + assertFalse("Token must not be registered until adding the first window", + mWms.isWindowToken(token)); + + final WindowManager.LayoutParams params = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + final View testView = new View(mWindowContext); + + final CountDownLatch latch = new CountDownLatch(1); + testView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + latch.countDown(); + } + + @Override + public void onViewDetachedFromWindow(View v) {} + }); + + mInstrumentation.runOnMainSync(() -> { + mWindowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); + }); + + + assertTrue(latch.await(4, TimeUnit.SECONDS)); + + + // Verify that the window token of the window context is created after first addView(). + assertTrue("Token must exist after adding the first view.", + mWms.isWindowToken(token)); + + mWindowContext.release(); + + // After the window context's release, the window token is also removed. + assertFalse("Token must be removed after release.", mWms.isWindowToken(token)); + } + + /** + * Verifies the behavior when window context attaches an {@link Activity} by override + * {@link WindowManager.LayoutParams#token}. + * + * The window context token should be overridden to + * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must + * not be removed regardless of the release of window context. + */ + @Test + public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease() + throws Throwable { + mActivityRule.launchActivity(new Intent()); + final Activity activity = mActivityRule.getActivity(); + final WindowManager.LayoutParams params = activity.getWindow().getAttributes(); + + final WindowContext windowContext = createWindowContext(params.type); + final IBinder token = windowContext.getWindowContextToken(); + + final View testView = new View(windowContext); + + mInstrumentation.runOnMainSync(() -> { + windowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); + }); + windowContext.release(); + + // Even if the window context is released, the activity should still exist. + assertTrue("Token must exist even if the window context is released.", + mWms.isWindowToken(activity.getActivityToken())); + } + + /** + * Verifies the behavior when window context attaches an existing token by override + * {@link WindowManager.LayoutParams#token}. + * + * The window context token should be overridden to + * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be + * removed regardless of release of window context. + */ + @Test + public void testCreateWindowContext_AttachWindowToken_TokenNotRemovedAfterRelease() + throws Throwable { + final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD); + final IBinder token = windowContext.getWindowContextToken(); + + final IBinder existingToken = new Binder(); + mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId()); + + final WindowManager.LayoutParams params = + new WindowManager.LayoutParams(TYPE_INPUT_METHOD); + params.token = existingToken; + final View testView = new View(windowContext); + + mInstrumentation.runOnMainSync(() -> { + windowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); + }); + windowContext.release(); + + // Even if the window context is released, the existing token should still exist. + assertTrue("Token must exist even if the window context is released.", + mWms.isWindowToken(existingToken)); + + mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY); + } + private WindowContext createWindowContext() { + return createWindowContext(TYPE_APPLICATION_OVERLAY); + } + + private WindowContext createWindowContext(@WindowType int type) { final Context instContext = mInstrumentation.getTargetContext(); final Display display = instContext.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); - final Context context = instContext.createDisplayContext(display); - return new WindowContext(context, TYPE_APPLICATION_OVERLAY, - null /* options */); + return (WindowContext) instContext.createWindowContext(display, type, null /* options */); } } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 8a512bc8834bc..cd18311d7d548 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.os.Process.INVALID_UID; -import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -26,7 +25,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT; -import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -41,7 +39,6 @@ import static com.android.server.wm.WindowTokenProto.WINDOW_CONTAINER; import android.annotation.CallSuper; import android.annotation.Nullable; -import android.app.IWindowToken; import android.app.servertransaction.FixedRotationAdjustmentsItem; import android.content.res.Configuration; import android.graphics.Rect; @@ -58,7 +55,6 @@ import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -112,17 +108,10 @@ class WindowToken extends WindowContainer { private FixedRotationTransformState mFixedRotationTransformState; - private Configuration mLastReportedConfig; - private int mLastReportedDisplay = INVALID_DISPLAY; - /** * When set to {@code true}, this window token is created from {@link android.app.WindowContext} */ - @VisibleForTesting - final boolean mFromClientToken; - - private DeathRecipient mDeathRecipient; - private boolean mBinderDied = false; + private final boolean mFromClientToken; private final int mOwnerUid; @@ -188,30 +177,6 @@ class WindowToken extends WindowContainer { } } - private class DeathRecipient implements IBinder.DeathRecipient { - private boolean mHasUnlinkToDeath = false; - - @Override - public void binderDied() { - synchronized (mWmService.mGlobalLock) { - mBinderDied = true; - removeImmediately(); - } - } - - void linkToDeath() throws RemoteException { - token.linkToDeath(DeathRecipient.this, 0); - } - - void unlinkToDeath() { - if (mHasUnlinkToDeath) { - return; - } - token.unlinkToDeath(DeathRecipient.this, 0); - mHasUnlinkToDeath = true; - } - } - /** * Compares two child window of this token and returns -1 if the first is lesser than the * second in terms of z-order and 1 otherwise. @@ -266,17 +231,6 @@ class WindowToken extends WindowContainer { if (dc != null) { dc.addWindowToken(token, this); } - if (shouldReportToClient()) { - try { - mDeathRecipient = new DeathRecipient(); - mDeathRecipient.linkToDeath(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to add window token with type " + windowType + " on " - + "display " + dc.getDisplayId(), e); - mDeathRecipient = null; - return; - } - } } void removeAllWindowsIfPossible() { @@ -414,22 +368,6 @@ class WindowToken extends WindowContainer { // Needs to occur after the token is removed from the display above to avoid attempt at // duplicate removal of this window container from it's parent. super.removeImmediately(); - - reportWindowTokenRemovedToClient(); - } - - // TODO(b/159767464): Remove after we migrate to listener approach. - private void reportWindowTokenRemovedToClient() { - if (!shouldReportToClient()) { - return; - } - mDeathRecipient.unlinkToDeath(); - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); - try { - windowTokenClient.onWindowTokenRemoved(); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); - } } @Override @@ -441,51 +379,11 @@ class WindowToken extends WindowContainer { // to another display before the window behind // it is ready. super.onDisplayChanged(dc); - reportConfigToWindowTokenClient(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - reportConfigToWindowTokenClient(); - } - - void reportConfigToWindowTokenClient() { - if (!shouldReportToClient()) { - return; - } - if (mLastReportedConfig == null) { - mLastReportedConfig = new Configuration(); - } - final Configuration config = getConfiguration(); - final int displayId = getDisplayContent().getDisplayId(); - if (config.diff(mLastReportedConfig) == 0 && displayId == mLastReportedDisplay) { - // No changes since last reported time. - return; - } - - mLastReportedConfig.setTo(config); - mLastReportedDisplay = displayId; - - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); - try { - windowTokenClient.onConfigurationChanged(config, displayId); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, - "Could not report config changes to the window token client."); - } - } - - /** - * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and - * registered from client side. - */ - private boolean shouldReportToClient() { - // Only report to client for WindowToken because Activities are updated through ATM - // callbacks. - return asActivityRecord() == null - // Report to {@link android.view.WindowTokenClient} if this token was registered from it. - && mFromClientToken && !mBinderDied; } @Override From 4af9f4fc2cd12562123792af38dbd082ea96b86b Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Fri, 4 Dec 2020 16:22:51 +0800 Subject: [PATCH 2/2] Apply WindowContext 2.0 for InputMethodMenuController Before WindowContext 2.0, WM creates WindowToken for each WindowContext. After that, it only creates WindowToken when WindowContext adss the first view. Therefore, if InputMethodMenuController invokes getSettingsContext with different display id before showing the IME switch dialog, moveWindowTokenToDisplay is no-op because WindowToken with token switchDialogToken hasn't yet existed. We should re-register to the different display to receive ImeContainer config changes for this scenario. Bug: 159767464 Bug: 153369119 Test: InputMethodMenuControllerTest Change-Id: Ib9d94a275e1b686535ab3edd7c0c3ce957efd8cd --- core/java/android/view/IWindowManager.aidl | 2 +- .../InputMethodMenuController.java | 8 ++----- .../server/wm/WindowManagerService.java | 15 +++++++++++- .../wm/InputMethodMenuControllerTest.java | 24 +++++++++---------- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 784341153d695..ae8afca9b5c5f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -117,7 +117,7 @@ interface IWindowManager // These can only be called when holding the MANAGE_APP_TOKENS permission. void setEventDispatching(boolean enabled); - /** @return {@code true} if this binder is a registered window token. */ + /** Returns {@code true} if this binder is a registered window token. */ boolean isWindowToken(in IBinder binder); /** * Adds window token for a given type. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 02a36dc74d686..f646d5d22263c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -220,7 +220,8 @@ public class InputMethodMenuController { */ @VisibleForTesting public Context getSettingsContext(int displayId) { - if (mSettingsContext == null) { + // TODO(b/178462039): Cover the case when IME is moved to another ImeContainer. + if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) { final Context systemUiContext = ActivityThread.currentActivityThread() .createSystemUiContext(displayId); final Context windowContext = systemUiContext.createWindowContext( @@ -229,11 +230,6 @@ public class InputMethodMenuController { windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings); mSwitchingDialogToken = mSettingsContext.getWindowContextToken(); } - // TODO(b/159767464): register the listener to another display again if window token is not - // yet created. - if (mSettingsContext.getDisplayId() != displayId) { - mWindowManagerInternal.moveWindowTokenToDisplay(mSwitchingDialogToken, displayId); - } return mSettingsContext; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 673c6a5fb470b..6319c80dc9bda 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -770,7 +770,8 @@ public class WindowManagerService extends IWindowManager.Stub final AnrController mAnrController; private final ScreenshotHashController mScreenshotHashController; - private final WindowContextListenerController mWindowContextListenerController = + @VisibleForTesting + final WindowContextListenerController mWindowContextListenerController = new WindowContextListenerController(); @VisibleForTesting @@ -2794,6 +2795,17 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_OKAY; } + /** + * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration + * changes of a {@link DisplayArea}. + * + * @param clientToken the window context's token + * @param type Window type of the window context + * @param displayId The display associated with the window context + * @param options A bundle used to pass window-related options and choose the right DisplayArea + * + * @return {@code true} if the listener was registered successfully. + */ @Override public boolean registerWindowContextListener(IBinder clientToken, int type, int displayId, Bundle options) { @@ -2849,6 +2861,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** Returns {@code true} if this binder is a registered window token. */ @Override public boolean isWindowToken(IBinder binder) { synchronized (mGlobalLock) { diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index f75c98f39323c..78074d6e1492a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -26,7 +27,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -70,13 +70,15 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { spyOn(wms); doAnswer(invocation -> { Object[] args = invocation.getArguments(); - final IBinder token = (IBinder) args[0]; - final int windowType = (int) args[1]; - new WindowToken(mWm, token, windowType, true /* persistOnEmpty */, - mDefaultDisplay, true /* ownerCanManageAppTokens */, 1000 /* ownerUid */, - false /* roundedCornerOverlay */, true /* fromClientToken */); - return WindowManagerGlobal.ADD_OKAY; - }).when(wms).addWindowTokenWithOptions(any(), anyInt(), anyInt(), any(), anyString()); + IBinder clientToken = (IBinder) args[0]; + int displayId = (int) args[2]; + DisplayContent dc = mWm.mRoot.getDisplayContent(displayId); + mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken, + dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, + null /* options */); + return true; + }).when(wms).registerWindowContextListener(any(), eq(TYPE_INPUT_METHOD_DIALOG), + anyInt(), any()); mSecondaryDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1000).build(); @@ -95,14 +97,12 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay); - // Obtain the context again and check they are the same instance and match the display - // metrics of the secondary display. + // Obtain the context again and check if the window metrics match the IME container bounds + // of the secondary display. final Context contextOnSecondaryDisplay = mController.getSettingsContext( mSecondaryDisplay.getDisplayId()); assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay); - assertThat(contextOnDefaultDisplay.getWindowContextToken()) - .isEqualTo(contextOnSecondaryDisplay.getWindowContextToken()); } private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {