diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 28093074303d00c8f82a1968eaa01fb3d1317e06..c6fe017b191794a9043137e17875ffe28a3c3c54 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4164,7 +4164,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } ProtoLog.i(WM_DEBUG_IME, "setInputMethodTarget %s", target); - final boolean layeringTargetChanged = target != mImeLayeringTarget; + boolean shouldUpdateImeParent = target != mImeLayeringTarget; mImeLayeringTarget = target; // 1. Reparent the IME container window to the target root DA to get the correct bounds and @@ -4172,10 +4172,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // is not organized (see FEATURE_IME and updateImeParent). if (target != null && !mImeWindowsContainer.isOrganized()) { RootDisplayArea targetRoot = target.getRootDisplayArea(); - if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) { - // Reposition the IME container to the target root to get the correct bounds and - // config. - targetRoot.placeImeContainer(mImeWindowsContainer); + if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea() + // Try reparent the IME container to the target root to get the bounds and + // config that match the target window. + && targetRoot.placeImeContainer(mImeWindowsContainer)) { + // Update the IME surface parent since the IME container window has been reparented. + shouldUpdateImeParent = true; // Directly hide the IME window so it doesn't flash immediately after reparenting. // InsetsController will make IME visible again before animating it. if (mInputMethodWindow != null) { @@ -4192,7 +4194,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // 4. Update the IME control target to apply any inset change and animation. // 5. Reparent the IME container surface to either the input target app, or the IME window // parent. - updateImeControlTarget(layeringTargetChanged); + updateImeControlTarget(shouldUpdateImeParent); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java index d94bb9e892523a875c53814c4e2ea0834fb6e91a..092adc37d497353bd7a7e5c10c79481e1514db6c 100644 --- a/services/core/java/com/android/server/wm/RootDisplayArea.java +++ b/services/core/java/com/android/server/wm/RootDisplayArea.java @@ -22,8 +22,10 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; +import android.util.Slog; import com.android.server.policy.WindowManagerPolicy; @@ -76,8 +78,10 @@ class RootDisplayArea extends DisplayArea.Dimmable { /** * Places the IME container below this root, so that it's bounds and config will be updated to * match the root. + * + * @return {@code true} if the IME container is reparented to this root. */ - void placeImeContainer(DisplayArea.Tokens imeContainer) { + boolean placeImeContainer(DisplayArea.Tokens imeContainer) { final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea(); List features = mFeatures; @@ -94,11 +98,23 @@ class RootDisplayArea extends DisplayArea.Dimmable { previousRoot.updateImeContainerForLayers(null /* imeContainer */); imeContainer.reparent(imeDisplayAreas.get(0), POSITION_TOP); updateImeContainerForLayers(imeContainer); - return; + return true; } } - throw new IllegalStateException( - "There is no FEATURE_IME_PLACEHOLDER in this root to place the IME container"); + + // Some device UX may not have the need to update the IME bounds and position for IME target + // in a child DisplayArea, so instead of throwing exception, we just allow the IME container + // to remain in its previous root. + if (!isDescendantOf(previousRoot)) { + // When this RootDisplayArea is a descendant of the current RootDisplayArea, it will be + // at the APPLICATION_LAYER, and the IME container will always be on top and have bounds + // equal or larger than the input target. + // If it is not a descendant, the DisplayAreaPolicy owner should make sure the IME is + // working correctly. Print a warning in case they are not. + Slog.w(TAG_WM, "The IME target is not in the same root as the IME container, but there " + + "is no DisplayArea of FEATURE_IME_PLACEHOLDER in the target RootDisplayArea"); + } + return false; } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index 2956c14155b938573d2b8686b2f0c759c4ed4077..ac3d0f0d3f28cde6163ae956efcbcb10e7a4b415 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -55,6 +56,8 @@ import android.window.IDisplayAreaOrganizer; import androidx.test.filters.SmallTest; +import com.google.android.collect.Lists; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -90,7 +93,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { @Before public void setUp() { // Let the Display to be created with the DualDisplay policy. - final DisplayAreaPolicy.Provider policyProvider = new DualDisplayTestPolicyProvider(); + setupDisplay(new DualDisplayTestPolicyProvider(mWm)); + } + + /** Populates fields for the test display. */ + private void setupDisplay(@NonNull DisplayAreaPolicy.Provider policyProvider) { doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait). @@ -382,6 +389,36 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { imeContainer.setOrganizer(null); } + @Test + public void testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder() { + // Define the DualDisplayArea hierarchy without IME_PLACEHOLDER in DAGs. + setupDisplay(new DualDisplayTestPolicyProvider(new ArrayList<>(), new ArrayList<>())); + setupImeWindow(); + final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); + final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); + + // By default, the ime container is attached to DC as defined in DAPolicy. + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); + + // firstActivityWin should be the target + final WindowState firstActivityWin = + createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, + "firstActivityWin"); + spyOn(firstActivityWin); + doReturn(true).when(firstActivityWin).canBeImeTarget(); + WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); + + // There is no IME_PLACEHOLDER in the firstRoot, so the ImeContainer will not be reparented. + assertThat(imeTarget).isEqualTo(firstActivityWin); + verify(mFirstRoot).placeImeContainer(imeContainer); + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); + assertThat(imeContainer.getParent().asDisplayArea().mFeatureId) + .isEqualTo(FEATURE_IME_PLACEHOLDER); + assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); + assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull(); + assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull(); + } + @Test public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -523,9 +560,37 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */ static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider { + @NonNull + private final List mFirstRootFeatures = new ArrayList<>(); + @NonNull + private final List mSecondRootFeatures = + new ArrayList<>(); + + DualDisplayTestPolicyProvider(@NonNull WindowManagerService wmService) { + // Add IME_PLACEHOLDER by default. + this(Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( + wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build()), + Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( + wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build())); + } + + DualDisplayTestPolicyProvider( + @NonNull List firstRootFeatures, + @NonNull List secondRootFeatures) { + mFirstRootFeatures.addAll(firstRootFeatures); + mSecondRootFeatures.addAll(secondRootFeatures); + } + @Override - public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, - RootDisplayArea root, DisplayArea.Tokens imeContainer) { + public DisplayAreaPolicy instantiate(@NonNull WindowManagerService wmService, + @NonNull DisplayContent content, @NonNull RootDisplayArea root, + @NonNull DisplayArea.Tokens imeContainer) { // Root // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation // layer @@ -554,12 +619,10 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { firstTdaList.add(firstTaskDisplayArea); DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy = new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) - .setTaskDisplayAreas(firstTdaList) - .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( - wmService.mPolicy, - "ImePlaceholder", FEATURE_IME_PLACEHOLDER) - .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) - .build()); + .setTaskDisplayAreas(firstTdaList); + for (DisplayAreaPolicyBuilder.Feature feature : mFirstRootFeatures) { + firstHierarchy.addFeature(feature); + } // Second final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot", @@ -570,12 +633,10 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { secondTdaList.add(secondTaskDisplayArea); DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy = new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot) - .setTaskDisplayAreas(secondTdaList) - .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( - wmService.mPolicy, - "ImePlaceholder", FEATURE_IME_PLACEHOLDER) - .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) - .build()); + .setTaskDisplayAreas(secondTdaList); + for (DisplayAreaPolicyBuilder.Feature feature : mSecondRootFeatures) { + secondHierarchy.addFeature(feature); + } return new DisplayAreaPolicyBuilder() .setRootHierarchy(rootHierarchy) diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java index d48711375d5cf8ad15c0b49cb86a6f9c9e6463dc..3090590938c0d682ddd9ad05328819f59778ce3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java @@ -78,7 +78,7 @@ public class InputMethodDialogWindowContextTest extends WindowTestsBase { public void setUp() throws Exception { // Let the Display be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = - new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); + new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm); Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); mWindowContext = new InputMethodDialogWindowContext(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index 1685673327951f07b1fcfb9c7228d825c27c27d0..f6d0bf110047418c12e9eaa11b242b53462f899f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -213,7 +213,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { public void testImeSwitchDialogWindowTokenRemovedOnDualDisplayContent_ListenToImeContainer() { // Let the Display to be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = - new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); + new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm); doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Create a DisplayContent with dual RootDisplayArea DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent =