Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 61c1234e authored by Chris Li's avatar Chris Li
Browse files

Fix IME container surface parent for dual-display-area

1. Loose the requirement of FEATURE_IME_PLACEHOLDER that the device can
   choose to not reparent the IME container based on their UX design.
2. Make sure the IME surface parent is up-to-date if the IME container
   window is reparented.

Bug: 243842191
Test: atest WmTests:DualDisplayAreaGroupPolicyTest
Change-Id: Ie51548a007a8eabd274a35db7dbfc26e3c956216
parent 624f3aef
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -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
+20 −4
Original line number Diff line number Diff line
@@ -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<Feature> 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;
            }
        }

        // 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");
        }
        throw new IllegalStateException(
                "There is no FEATURE_IME_PLACEHOLDER in this root to place the IME container");
        return false;
    }

    /**
+76 −15
Original line number Diff line number Diff line
@@ -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<DisplayAreaPolicyBuilder.Feature> mFirstRootFeatures = new ArrayList<>();
        @NonNull
        private final List<DisplayAreaPolicyBuilder.Feature> 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<DisplayAreaPolicyBuilder.Feature> firstRootFeatures,
                @NonNull List<DisplayAreaPolicyBuilder.Feature> 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)
+1 −1
Original line number Diff line number Diff line
@@ -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();
+1 −1
Original line number Diff line number Diff line
@@ -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 =