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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user