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

Commit f68fbed3 authored by Romica Iordan's avatar Romica Iordan Committed by Chris Li
Browse files

Enable vendors to organize ImeContainer

Add FEATURE_IME as unique id for ImeContainer to organize it from
WmShell.
Disable automatic ImeContainer surface and container reparenting when
it is organized.

These changes enable vendors to control where the IMEs are positioned
and implement custom UX rules (e.g. for foldable devices - place the
IME on the bottom screen while the focused app is on the top one).

Manual test:
Organize FEATURE_IME from WmShell.
Set its bounds, appBounds, screenSizeDp and setSmallestScreenWidthDp.
Outcome:
* The IME receives onConfigurationChanges.
* The IME is updated on the screen.
* The IME is not reparented from its default parent.

Test: atest WmTests:WindowProcessControllerTests
Test: atest WmTests:DisplayContentTests
Test: atest WmTests:WindowContainerTests
Test: atest WmTests:DisplayAreaTest
Test: atest WmTests:DualDisplayAreaGroupPolicyTest
Bug: 188038793

Change-Id: I438253e9647ceab5b7847b8db66db8399271723f
parent 2967bebf
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -108,6 +108,18 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
     */
    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;

    /**
     * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented
     * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea.
     *
     * This feature can register organizers in order to disable the reparenting logic and manage
     * the position and settings of the container manually. This is useful for foldable devices
     * which require custom UX rules for the IME position (e.g. IME on one screen and the focused
     * app on another screen).
     * @hide
     */
    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9;

    /**
     * The last boundary of display area for system features
     */
+5 −1
Original line number Diff line number Diff line
@@ -586,7 +586,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
        };

        Tokens(WindowManagerService wms, Type type, String name) {
            super(wms, type, name, FEATURE_WINDOW_TOKENS);
            this(wms, type, name, FEATURE_WINDOW_TOKENS);
        }

        Tokens(WindowManagerService wms, Type type, String name, int featureId) {
            super(wms, type, name, featureId);
        }

        void addChild(WindowToken token) {
+36 −4
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;

@@ -632,7 +633,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    @interface InputMethodTarget {}

    /** The surface parent of the IME container. */
    private SurfaceControl mInputMethodSurfaceParent;
    @VisibleForTesting
    SurfaceControl mInputMethodSurfaceParent;

    /** The screenshot IME surface to place on the task while transitioning to the next task. */
    SurfaceControl mImeScreenshot;
@@ -3831,6 +3833,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    }

    boolean shouldImeAttachedToApp() {
        if (mImeWindowsContainer.isOrganized()) {
            return false;
        }

        // Force attaching IME to the display when magnifying, or it would be magnified with
        // target app together.
        final boolean allowAttachToApp = (mMagnificationSpec == null);
@@ -3959,8 +3965,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        mImeLayeringTarget = target;

        // 1. Reparent the IME container window to the target root DA to get the correct bounds and
        // config. (Only happens when the target window is in a different root DA)
        if (target != null) {
        // config. Only happens when the target window is in a different root DA and ImeContainer
        // 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
@@ -4144,6 +4151,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    }

    void updateImeParent() {
        if (mImeWindowsContainer.isOrganized()) {
            if (DEBUG_INPUT_METHOD) {
                Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent.");
            }
            // Leave the ImeContainer where the DisplayAreaPolicy placed it.
            // FEATURE_IME is organized by vendor so they are responible for placing the surface.
            mInputMethodSurfaceParent = null;
            return;
        }

        final SurfaceControl newParent = computeImeParent();
        if (newParent != null && newParent != mInputMethodSurfaceParent) {
            mInputMethodSurfaceParent = newParent;
@@ -4750,7 +4767,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        boolean mNeedsLayer = false;

        ImeContainer(WindowManagerService wms) {
            super(wms, Type.ABOVE_TASKS, "ImeContainer");
            super(wms, Type.ABOVE_TASKS, "ImeContainer", FEATURE_IME);
        }

        public void setNeedsLayer() {
@@ -4811,6 +4828,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            super.assignRelativeLayer(t, relativeTo, layer, forceUpdate);
            mNeedsLayer = false;
        }

        @Override
        void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) {
            super.setOrganizer(organizer, skipDisplayAreaAppeared);
            mDisplayContent.updateImeParent();
        }
    }

    @Override
@@ -4909,6 +4932,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    }

    private void assignRelativeLayerForIme(SurfaceControl.Transaction t, boolean forceUpdate) {
        if (mImeWindowsContainer.isOrganized()) {
            if (DEBUG_INPUT_METHOD) {
                Slog.i(TAG_WM, "ImeContainer is organized. Skip assignRelativeLayerForIme.");
            }
            // Leave the ImeContainer where the DisplayAreaPolicy placed it.
            // When using FEATURE_IME, Organizer assumes the responsibility for placing the surface.
            return;
        }

        mImeWindowsContainer.setNeedsLayer();
        final WindowState imeTarget = mImeLayeringTarget;
        // In the case where we have an IME target that is not in split-screen mode IME
+28 −0
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;

import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
@@ -134,6 +135,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
import android.window.IDisplayAreaOrganizer;
import android.window.WindowContainerToken;

import androidx.test.filters.SmallTest;
@@ -390,6 +392,32 @@ public class DisplayContentTests extends WindowTestsBase {
        assertNull("computeImeParent() should be null", mDisplayContent.computeImeParent());
    }

    @Test
    public void testUpdateImeParent_skipForOrganizedImeContainer() {
        final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
        final ActivityRecord activity = createActivityRecord(mDisplayContent);

        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
                "startingWin");
        startingWin.setHasSurface(true);
        assertTrue(startingWin.canBeImeTarget());
        final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class);
        doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent();

        // Main precondition for this test: organize the ImeContainer.
        final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class);
        when(mockImeOrganizer.asBinder()).thenReturn(new Binder());
        imeContainer.setOrganizer(mockImeOrganizer);

        mDisplayContent.updateImeParent();

        assertNull("Don't reparent the surface of an organized ImeContainer.",
                mDisplayContent.mInputMethodSurfaceParent);

        // Clean up organizer.
        imeContainer.setOrganizer(null);
    }

    /**
     * This tests root task movement between displays and proper root task's, task's and app token's
     * display container references updates.
+36 −0
Original line number Diff line number Diff line
@@ -41,14 +41,17 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.window.IDisplayAreaOrganizer;

import androidx.test.filters.SmallTest;

@@ -345,6 +348,39 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */);
    }

    @Test
    public void testPlaceImeContainer_skipReparentForOrganizedImeContainer() {
        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);
        assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);

        final WindowState firstActivityWin =
                createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
                        "firstActivityWin");
        spyOn(firstActivityWin);
        // firstActivityWin should be the target
        doReturn(true).when(firstActivityWin).canBeImeTarget();

        // Main precondition for this test: organize the ImeContainer.
        final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class);
        when(mockImeOrganizer.asBinder()).thenReturn(new Binder());
        imeContainer.setOrganizer(mockImeOrganizer);

        WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);

        // The IME target must be updated but the don't reparent organized ImeContainers.
        // See DisplayAreaOrganizer#FEATURE_IME.
        assertThat(imeTarget).isEqualTo(firstActivityWin);
        verify(mFirstRoot, never()).placeImeContainer(imeContainer);

        // Clean up organizer.
        imeContainer.setOrganizer(null);
    }

    @Test
    public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
        mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);