Merge "Move PiP window upon tabletop mode changes" into tm-qpr-dev

This commit is contained in:
Hongwei Wang
2023-03-14 22:39:50 +00:00
committed by Android (Google) Code Review
5 changed files with 118 additions and 5 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}
/**

View File

@@ -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() {

View File

@@ -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