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

Commit fa5e4152 authored by Felix Stern's avatar Felix Stern Committed by Android (Google) Code Review
Browse files

Merge "Exclude IME insets in InsetsPolicy when the task is in split screen" into main

parents 33208267 df1a1361
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
@@ -44,6 +45,7 @@ import android.util.ArrayMap;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;

import com.android.window.flags.Flags;
@@ -266,6 +268,23 @@ public final class WindowContainerTransaction implements Parcelable {
        return this;
    }

    /**
     * Sets whether the IME insets should be excluded by {@link com.android.server.wm.InsetsPolicy}.
     * @hide
     */
    @SuppressLint("UnflaggedApi")
    @NonNull
    public WindowContainerTransaction setExcludeImeInsets(
            @NonNull WindowContainerToken container, boolean exclude) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES)
                        .setContainer(container.asBinder())
                        .setExcludeInsetsTypes(exclude ? WindowInsets.Type.ime() : 0)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets whether a container or its children should be hidden. When {@code false}, the existing
     * visibility of the container applies, but when {@code true} the container will be forced
@@ -1449,6 +1468,7 @@ public final class WindowContainerTransaction implements Parcelable {
        public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
        public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
        public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
        public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;

        // The following key(s) are for use with mLaunchOptions:
        // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1512,6 +1532,8 @@ public final class WindowContainerTransaction implements Parcelable {

        private boolean mIsTrimmableFromRecents;

        private @InsetsType int mExcludeInsetsTypes;

        public static HierarchyOp createForReparent(
                @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1649,6 +1671,7 @@ public final class WindowContainerTransaction implements Parcelable {
            mAlwaysOnTop = copy.mAlwaysOnTop;
            mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
            mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
            mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
        }

        protected HierarchyOp(Parcel in) {
@@ -1671,6 +1694,7 @@ public final class WindowContainerTransaction implements Parcelable {
            mAlwaysOnTop = in.readBoolean();
            mReparentLeafTaskIfRelaunch = in.readBoolean();
            mIsTrimmableFromRecents = in.readBoolean();
            mExcludeInsetsTypes = in.readInt();
        }

        public int getType() {
@@ -1772,6 +1796,10 @@ public final class WindowContainerTransaction implements Parcelable {
            return mIsTrimmableFromRecents;
        }

        public @InsetsType int getExcludeInsetsTypes() {
            return mExcludeInsetsTypes;
        }

        /** Gets a string representation of a hierarchy-op type. */
        public static String hopToString(int type) {
            switch (type) {
@@ -1795,6 +1823,7 @@ public final class WindowContainerTransaction implements Parcelable {
                    return "setReparentLeafTaskIfRelaunch";
                case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                    return "addTaskFragmentOperation";
                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
                default: return "HOP(" + type + ")";
            }
        }
@@ -1868,6 +1897,11 @@ public final class WindowContainerTransaction implements Parcelable {
                    sb.append("fragmentToken= ").append(mContainer)
                            .append(" operation= ").append(mTaskFragmentOperation);
                    break;
                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES:
                    sb.append("container= ").append(mContainer)
                            .append(" mExcludeInsetsTypes= ")
                            .append(WindowInsets.Type.toString(mExcludeInsetsTypes));
                    break;
                case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
                    sb.append("container= ").append(mContainer)
                            .append(" isTrimmable= ")
@@ -1903,6 +1937,7 @@ public final class WindowContainerTransaction implements Parcelable {
            dest.writeBoolean(mAlwaysOnTop);
            dest.writeBoolean(mReparentLeafTaskIfRelaunch);
            dest.writeBoolean(mIsTrimmableFromRecents);
            dest.writeInt(mExcludeInsetsTypes);
        }

        @Override
@@ -1974,6 +2009,8 @@ public final class WindowContainerTransaction implements Parcelable {

            private boolean mIsTrimmableFromRecents;

            private @InsetsType int mExcludeInsetsTypes;

            Builder(int type) {
                mType = type;
            }
@@ -2069,6 +2106,11 @@ public final class WindowContainerTransaction implements Parcelable {
                return this;
            }

            Builder setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
                mExcludeInsetsTypes = excludeInsetsTypes;
                return this;
            }

            HierarchyOp build() {
                final HierarchyOp hierarchyOp = new HierarchyOp(mType);
                hierarchyOp.mContainer = mContainer;
@@ -2093,6 +2135,7 @@ public final class WindowContainerTransaction implements Parcelable {
                hierarchyOp.mIncludingParents = mIncludingParents;
                hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
                hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
                hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;

                return hierarchyOp;
            }
+20 −0
Original line number Diff line number Diff line
@@ -157,6 +157,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
        }
    }

    private void dispatchImeRequested(int displayId, boolean isRequested) {
        synchronized (mPositionProcessors) {
            for (ImePositionProcessor pp : mPositionProcessors) {
                pp.onImeRequested(displayId, isRequested);
            }
        }
    }

    @ImePositionProcessor.ImeAnimationFlags
    private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop,
            boolean show, boolean isFloating, SurfaceControl.Transaction t) {
@@ -398,6 +406,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
        public void setImeInputTargetRequestedVisibility(boolean visible) {
            if (android.view.inputmethod.Flags.refactorInsetsController()) {
                mImeRequestedVisible = visible;
                dispatchImeRequested(mDisplayId, mImeRequestedVisible);

                // In the case that the IME becomes visible, but we have the control with leash
                // already (e.g., when focussing an editText in activity B, while and editText in
                // activity A is focussed), we will not get a call of #insetsControlChanged, and
@@ -446,6 +456,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
            if (imeSource == null || mImeSourceControl == null) {
                return;
            }
            // TODO(b/353463205): For hide: this still has the statsToken from the previous show
            //  request
            final var statsToken = mImeSourceControl.getImeStatsToken();

            startAnimation(show, forceRestart, statsToken);
@@ -705,6 +717,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
        @interface ImeAnimationFlags {
        }

        /**
         * Called when the IME was requested by an app
         *
         * @param isRequested {@code true} if the IME was requested to be visible
         */
        default void onImeRequested(int displayId, boolean isRequested) {
        }

        /**
         * Called when the IME position is starting to animate.
         *
+29 −0
Original line number Diff line number Diff line
@@ -1106,6 +1106,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        default void onDoubleTappedDivider() {
        }

        /**
         * Sets the excludedInsetsTypes for the IME in the root WindowContainer.
         */
        void setExcludeImeInsets(boolean exclude);

        /** Returns split position of the token. */
        @SplitPosition
        int getSplitItemPosition(WindowContainerToken token);
@@ -1304,6 +1309,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
            mDisplayId = displayId;
        }

        @Override
        public void onImeRequested(int displayId, boolean isRequested) {
            if (displayId != mDisplayId) return;
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s",
                    isRequested);
            mSplitLayoutHandler.setExcludeImeInsets(true);
        }

        @Override
        public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
@@ -1356,6 +1369,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
            setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
                    "onImeStartPositioning");

            if (android.view.inputmethod.Flags.refactorInsetsController()) {
                if (mImeShown) {
                    mSplitLayoutHandler.setExcludeImeInsets(false);
                }
            }

            return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
        }

@@ -1374,6 +1393,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
                    "Split IME animation ending, canceled=%b", cancel);
            onProgress(1.0f);
            mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
            if (android.view.inputmethod.Flags.refactorInsetsController()) {
                if (!mImeShown) {
                    // The IME hide animation is started immediately and at that point, the IME
                    // insets are not yet set to hidden. Therefore only resetting the
                    // excludedTypes at the end of the animation. Note: InsetsPolicy will only
                    // set the IME height to zero, when it is visible. When it becomes invisible,
                    // we dispatch the insets (the height there is zero as well)
                    mSplitLayoutHandler.setExcludeImeInsets(false);
                }
            }
        }

        @Override
+18 −0
Original line number Diff line number Diff line
@@ -901,6 +901,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        setEnterInstanceId(instanceId);
    }


    @Override
    public void setExcludeImeInsets(boolean exclude) {
        if (android.view.inputmethod.Flags.refactorInsetsController()) {
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            if (mRootTaskInfo == null) {
                ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "setExcludeImeInsets: mRootTaskInfo is null");
                return;
            }
            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                    "setExcludeImeInsets: root taskId=%s exclude=%s",
                    mRootTaskInfo.taskId, exclude);
            wct.setExcludeImeInsets(mRootTaskInfo.token, exclude);
            mTaskOrganizer.applyTransaction(wct);
        }
    }

    /**
     * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
     * launch the non-pipped app as a fullscreen app, otherwise no-op.
@@ -1717,6 +1734,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
        wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);

        // Make the stages adjacent to each other so they occlude what's behind them.
        wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
        setRootForceTranslucent(true, wct);
+39 −3
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -34,6 +36,12 @@ import static org.mockito.Mockito.verifyZeroInteractions;

import android.graphics.Insets;
import android.graphics.Point;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -47,6 +55,7 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,11 +70,16 @@ import java.util.concurrent.Executor;
 */
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    @Mock
    private SurfaceControl.Transaction mT;
    @Mock
    private ShellInit mShellInit;
    @Mock
    private IWindowManager mWm;
    private DisplayImeController mDisplayImeController;
    private DisplayImeController.PerDisplay mPerDisplay;
    private Executor mExecutor;

@@ -73,7 +87,8 @@ public class DisplayImeControllerTest extends ShellTestCase {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mExecutor = spy(Runnable::run);
        mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
        mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null,
                new TransactionPool() {
            @Override
            public SurfaceControl.Transaction acquire() {
                return mT;
@@ -84,8 +99,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
            }
        }, mExecutor) {
            @Override
            void removeImeSurface(int displayId) { }
        }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
            void removeImeSurface(int displayId) {
            }
        };
        mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
    }

    @Test
@@ -95,12 +112,14 @@ public class DisplayImeControllerTest extends ShellTestCase {

    @Test
    public void insetsControlChanged_schedulesNoWorkOnExecutor() {
        Looper.prepare();
        mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
        verifyZeroInteractions(mExecutor);
    }

    @Test
    public void insetsChanged_schedulesNoWorkOnExecutor() {
        Looper.prepare();
        mPerDisplay.insetsChanged(insetsStateWithIme(false));
        verifyZeroInteractions(mExecutor);
    }
@@ -117,7 +136,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
        verifyZeroInteractions(mExecutor);
    }

    // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
    // this test is obsolete
    @Test
    @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
    public void reappliesVisibilityToChangedLeash() {
        verifyZeroInteractions(mT);
        mPerDisplay.mImeShowing = false;
@@ -136,6 +158,7 @@ public class DisplayImeControllerTest extends ShellTestCase {

    @Test
    public void insetsControlChanged_updateImeSourceControl() {
        Looper.prepare();
        mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
        assertNotNull(mPerDisplay.mImeSourceControl);

@@ -143,6 +166,19 @@ public class DisplayImeControllerTest extends ShellTestCase {
        assertNull(mPerDisplay.mImeSourceControl);
    }

    @Test
    @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
    public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() {
        var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
        mDisplayImeController.addPositionProcessor(mockPp);

        mPerDisplay.setImeInputTargetRequestedVisibility(true);
        verify(mockPp).onImeRequested(anyInt(), eq(true));

        mPerDisplay.setImeInputTargetRequestedVisibility(false);
        verify(mockPp).onImeRequested(anyInt(), eq(false));
    }

    private InsetsSourceControl[] insetsSourceControl() {
        return new InsetsSourceControl[]{
                new InsetsSourceControl(
Loading