diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b94721e37138b..59727d5b25559 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2467,6 +2467,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "781471998": { + "message": "moveWindowTokenToDisplay: Cannot move to the original display for token: %s", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "791468751": { "message": "Pausing rotation during re-position", "level": "DEBUG", @@ -2671,6 +2677,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1033274509": { + "message": "moveWindowTokenToDisplay: Attempted to move non-existing token: %s", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "1040675582": { "message": "Can't report activity configuration update - client not running, activityRecord=%s", "level": "WARN", @@ -3457,6 +3469,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" }, + "2060978050": { + "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "2081291430": { "message": "Focus not requested for window=%s because it has no surface", "level": "DEBUG", diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 6fb887409ac54..d94d78e7bbaf7 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -136,10 +136,7 @@ public class InputMethodMenuController { } } - final ActivityThread currentThread = ActivityThread.currentActivityThread(); - final Context settingsContext = new ContextThemeWrapper( - currentThread.createSystemUiContext(displayId), - com.android.internal.R.style.Theme_DeviceDefault_Settings); + final Context settingsContext = getSettingsContext(displayId); mDialogBuilder = new AlertDialog.Builder(settingsContext); mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu()); @@ -214,6 +211,32 @@ public class InputMethodMenuController { } } + /** + * Returns the window context for IME switch dialogs to receive configuration changes. + * + * This method initializes the window context if it was not initialized. This method also moves + * the context to the targeted display if the current display of context is different than + * the display specified by {@code displayId}. + */ + @VisibleForTesting + public Context getSettingsContext(int displayId) { + if (mSettingsContext == null) { + final Context systemUiContext = ActivityThread.currentActivityThread() + .createSystemUiContext(displayId); + final Context windowContext = systemUiContext.createWindowContext( + WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */); + mSettingsContext = new ContextThemeWrapper( + windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings); + mSwitchingDialogToken = mSettingsContext.getActivityToken(); + } + // 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; + } + private boolean isScreenLocked() { return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 315014c1b248c..66cb674f0acd1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -597,4 +597,9 @@ public abstract class WindowManagerInternal { * @return The corresponding {@link WindowState#getName()} */ public abstract @Nullable String getImeTargetNameForLogging(int displayId); + + /** + * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}. + */ + public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 19cfcb21c8ac0..366cf945db82a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2723,6 +2723,32 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** @see WindowManagerInternal#moveWindowTokenToDisplay(IBinder, int) */ + public void moveWindowTokenToDisplay(IBinder binder, int displayId) { + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + ProtoLog.w(WM_ERROR, "moveWindowTokenToDisplay: Attempted to move token: %s" + + " to non-exiting displayId=%d", binder, displayId); + return; + } + final WindowToken token = mRoot.getWindowToken(binder); + if (token == null) { + ProtoLog.w(WM_ERROR, + "moveWindowTokenToDisplay: Attempted to move non-existing token: %s", + binder); + return; + } + if (token.getDisplayContent() == dc) { + ProtoLog.w(WM_ERROR, + "moveWindowTokenToDisplay: Cannot move to the original display " + + "for token: %s", binder); + return; + } + dc.reParentWindowToken(token); + } + } + void setNewDisplayOverrideConfiguration(Configuration overrideConfig, @NonNull DisplayContent dc) { if (dc.mWaitingForConfig) { @@ -7355,6 +7381,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void moveWindowTokenToDisplay(IBinder binder, int displayId) { + WindowManagerService.this.moveWindowTokenToDisplay(binder, displayId); + } + // TODO(multi-display): currently only used by PWM to notify keyguard transitions as well // forwarding it to SystemUI for synchronizing status and navigation bar animations. @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java new file mode 100644 index 0000000000000..0a960bef015e3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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 com.android.server.wm; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.WindowManager; + +import com.android.server.inputmethod.InputMethodManagerService; +import com.android.server.inputmethod.InputMethodMenuController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +// TODO(b/157888351): Move the test to inputmethod package once we find the way to test the +// scenario there. +/** + * Build/Install/Run: + * atest WmTests:InputMethodMenuControllerTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class InputMethodMenuControllerTest extends WindowTestsBase { + + private InputMethodMenuController mController; + + @Before + public void setUp() { + mController = new InputMethodMenuController(mock(InputMethodManagerService.class)); + } + + @Test + public void testGetSettingsContext() { + final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY); + + assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay); + + // Obtain the context again and check they are the same instance and match the display + // metrics of the secondary display. + final Context contextOnSecondaryDisplay = mController.getSettingsContext( + mDisplayContent.getDisplayId()); + + assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mDisplayContent); + assertThat(contextOnDefaultDisplay.getActivityToken()) + .isEqualTo(contextOnSecondaryDisplay.getActivityToken()); + } + + private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) { + assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId()); + + final Rect contextBounds = context.getSystemService(WindowManager.class) + .getMaximumWindowMetrics().getBounds(); + final Rect imeContainerBounds = dc.getImeContainer().getBounds(); + assertThat(contextBounds).isEqualTo(imeContainerBounds); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 5b7cf5a72f0a4..749f33e9fec20 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Process.INVALID_UID; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; @@ -32,8 +34,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -176,4 +181,31 @@ public class WindowManagerServiceTests extends WindowTestsBase { mWm.dismissKeyguard(null, "test-dismiss-keyguard"); verify(mWm.mAtmService.mStackSupervisor).wakeUp(anyString()); } + + @Test + public void testMoveWindowTokenToDisplay_NullToken_DoNothing() { + mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId()); + + verify(mDisplayContent, never()).reParentWindowToken(any()); + } + + @Test + public void testMoveWindowTokenToDisplay_SameDisplay_DoNothing() { + final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD_DIALOG, + mDisplayContent); + + mWm.moveWindowTokenToDisplay(windowToken.token, mDisplayContent.getDisplayId()); + + verify(mDisplayContent, never()).reParentWindowToken(any()); + } + + @Test + public void testMoveWindowTokenToDisplay_DifferentDisplay_DoMoveDisplay() { + final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD_DIALOG, + mDisplayContent); + + mWm.moveWindowTokenToDisplay(windowToken.token, DEFAULT_DISPLAY); + + assertThat(windowToken.getDisplayContent()).isEqualTo(mDefaultDisplay); + } }