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

Commit 38cb69d2 authored by Hongwei Wang's avatar Hongwei Wang
Browse files

Move PiP window upon tabletop mode changes

System property persist.wm.debug.enable_move_floating_window_in_tabletop
can be used to toggle the feature.

Further, System property persist.wm.debug.prefer_top_half_in_tabletop
can be used to control which half is preferred. This preferred location
is used for moving the PiP and affects the default position as well.

This feature depends on the keep-clear-area experiment.

Video: http://recall/-/aaaaaabFQoRHlzixHdtY/ccvYWU8bJ7EycW396QEijm
Video: http://recall/-/aaaaaabFQoRHlzixHdtY/hvQVa0uXCPm2RoDuadZuCA
Bug: 260871991
Test: Manual, toggle the system property for both cases, see videos
Merged-In: Ie7ae440847c81d0a49ccd56061673d86977b3b64
Change-Id: Ie7ae440847c81d0a49ccd56061673d86977b3b64
parent 4626fa48
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
@@ -22,10 +22,12 @@ import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE
import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Configuration;
import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Surface;

@@ -34,6 +36,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -49,8 +53,34 @@ import java.util.Set;
public class TabletopModeController implements
        DevicePostureController.OnDevicePostureChangedListener,
        DisplayController.OnDisplaysChangedListener {
    /**
     * When {@code true}, floating windows like PiP would auto move to the position
     * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
     */
    private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
            SystemProperties.getBoolean(
                    "persist.wm.debug.enable_move_floating_window_in_tabletop", false);

    /**
     * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
     * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
     * See also {@link #getPreferredHalfInTabletopMode()}.
     */
    private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
            SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);

    private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;

    @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
            PREFERRED_TABLETOP_HALF_TOP,
            PREFERRED_TABLETOP_HALF_BOTTOM
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface PreferredTabletopHalf {}

    public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
    public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;

    private final Context mContext;

    private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@ public class TabletopModeController implements
        }
    }

    /**
     * @return {@code true} if floating windows like PiP would auto move to the position
     * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
     */
    public boolean enableMoveFloatingWindowInTabletop() {
        return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
    }

    /** @return Preferred half for floating windows like PiP when in tabletop mode. */
    @PreferredTabletopHalf
    public int getPreferredHalfInTabletopMode() {
        return PREFER_TOP_HALF_IN_TABLETOP
                ? PREFERRED_TABLETOP_HALF_TOP
                : PREFERRED_TABLETOP_HALF_BOTTOM;
    }

    /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
    public void registerOnTabletopModeChangedListener(
            @NonNull OnTabletopModeChangedListener listener) {
+3 −1
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -353,6 +354,7 @@ public abstract class WMShellModule {
            TaskStackListenerImpl taskStackListener,
            PipParamsChangedForwarder pipParamsChangedForwarder,
            DisplayInsetsController displayInsetsController,
            TabletopModeController pipTabletopController,
            Optional<OneHandedController> oneHandedController,
            @ShellMainThread ShellExecutor mainExecutor) {
        return Optional.ofNullable(PipController.create(
@@ -362,7 +364,7 @@ public abstract class WMShellModule {
                pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
                taskStackListener, pipParamsChangedForwarder, displayInsetsController,
                oneHandedController, mainExecutor));
                pipTabletopController, oneHandedController, mainExecutor));
    }

    @WMSingleton
+22 −1
Original line number Diff line number Diff line
@@ -44,7 +44,9 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -115,6 +117,12 @@ public class PipBoundsState {
     * @see android.view.View#setPreferKeepClearRects
     */
    private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
    /**
     * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
     * as unrestricted keep clear area. Values in this map would be appended to
     * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
     */
    private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();

    private @Nullable Runnable mOnMinimalSizeChangeCallback;
    private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -393,6 +401,16 @@ public class PipBoundsState {
        mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
    }

    /** Add a named unrestricted keep clear area. */
    public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
        mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
    }

    /** Remove a named unrestricted keep clear area. */
    public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
        mNamedUnrestrictedKeepClearAreas.remove(name);
    }

    @NonNull
    public Set<Rect> getRestrictedKeepClearAreas() {
        return mRestrictedKeepClearAreas;
@@ -400,7 +418,10 @@ public class PipBoundsState {

    @NonNull
    public Set<Rect> getUnrestrictedKeepClearAreas() {
        return mUnrestrictedKeepClearAreas;
        if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
        final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
        unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
        return unrestrictedAreas;
    }

    /**
+43 −1
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -71,6 +72,7 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -145,6 +147,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
    private TaskStackListenerImpl mTaskStackListener;
    private PipParamsChangedForwarder mPipParamsChangedForwarder;
    private DisplayInsetsController mDisplayInsetsController;
    private TabletopModeController mTabletopModeController;
    private Optional<OneHandedController> mOneHandedController;
    private final ShellCommandHandler mShellCommandHandler;
    private final ShellController mShellController;
@@ -400,6 +403,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
            TaskStackListenerImpl taskStackListener,
            PipParamsChangedForwarder pipParamsChangedForwarder,
            DisplayInsetsController displayInsetsController,
            TabletopModeController pipTabletopController,
            Optional<OneHandedController> oneHandedController,
            ShellExecutor mainExecutor) {
        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -414,7 +418,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
                pipTransitionState, pipTouchHandler, pipTransitionController,
                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
                displayInsetsController, oneHandedController, mainExecutor)
                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
                .mImpl;
    }

@@ -440,6 +444,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
            TaskStackListenerImpl taskStackListener,
            PipParamsChangedForwarder pipParamsChangedForwarder,
            DisplayInsetsController displayInsetsController,
            TabletopModeController tabletopModeController,
            Optional<OneHandedController> oneHandedController,
            ShellExecutor mainExecutor
    ) {
@@ -472,6 +477,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
                .getInteger(R.integer.config_pipEnterAnimationDuration);
        mPipParamsChangedForwarder = pipParamsChangedForwarder;
        mDisplayInsetsController = displayInsetsController;
        mTabletopModeController = tabletopModeController;

        shellInit.addInitCallback(this::onInit, this);
    }
@@ -655,6 +661,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb
                    }
                });

        mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
            if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
            final String tag = "tabletop-mode";
            if (!isInTabletopMode) {
                mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
                return;
            }

            // To prepare for the entry bounds.
            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
            if (mTabletopModeController.getPreferredHalfInTabletopMode()
                    == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
                // Prefer top, avoid the bottom half of the display.
                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
                        displayBounds.left, displayBounds.centerY(),
                        displayBounds.right, displayBounds.bottom));
            } else {
                // Prefer bottom, avoid the top half of the display.
                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
                        displayBounds.left, displayBounds.top,
                        displayBounds.right, displayBounds.centerY()));
            }

            // Try to move the PiP window if we have entered PiP mode.
            if (mPipTransitionState.hasEnteredPip()) {
                final Rect pipBounds = mPipBoundsState.getBounds();
                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
                if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
                    // PiP bounds is too big to fit either half, bail early.
                    return;
                }
                mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
                mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
            }
        });

        mOneHandedController.ifPresent(controller -> {
            controller.registerTransitionCallback(
                    new OneHandedTransitionCallback() {
+4 −2
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
@@ -113,6 +114,7 @@ public class PipControllerTest extends ShellTestCase {
    @Mock private Optional<OneHandedController> mMockOneHandedController;
    @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
    @Mock private DisplayInsetsController mMockDisplayInsetsController;
    @Mock private TabletopModeController mMockTabletopModeController;

    @Mock private DisplayLayout mMockDisplayLayout1;
    @Mock private DisplayLayout mMockDisplayLayout2;
@@ -135,7 +137,7 @@ public class PipControllerTest extends ShellTestCase {
                mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
                mMockWindowManagerShellWrapper, mMockTaskStackListener,
                mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
                mMockOneHandedController, mMockExecutor);
                mMockTabletopModeController, mMockOneHandedController, mMockExecutor);
        mShellInit.init();
        when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
        when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -226,7 +228,7 @@ public class PipControllerTest extends ShellTestCase {
                mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
                mMockWindowManagerShellWrapper, mMockTaskStackListener,
                mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
                mMockOneHandedController, mMockExecutor));
                mMockTabletopModeController, mMockOneHandedController, mMockExecutor));
    }

    @Test