Merge changes from topic "new-size-spec" into tm-qpr-dev

* changes:
  Fix getOverrideMinSize to not go beyond min bounds
  Update the pip size spec logic
This commit is contained in:
Ikram Gabiyev
2023-02-03 06:03:54 +00:00
committed by Android (Google) Code Review
19 changed files with 948 additions and 267 deletions

View File

@@ -105,6 +105,10 @@
1.777778 1.777778
</item> </item>
<!-- The aspect ratio that by which optimizations to large screen sizes are made.
Needs to be less that or equal to 1. -->
<item name="config_pipLargeScreenOptimizedAspectRatio" format="float" type="dimen">0.5625</item>
<!-- The default gravity for the picture-in-picture window. <!-- The default gravity for the picture-in-picture window.
Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT --> Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
<integer name="config_defaultPictureInPictureGravity">0x55</integer> <integer name="config_defaultPictureInPictureGravity">0x55</integer>

View File

@@ -39,6 +39,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState; import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -69,6 +70,7 @@ public abstract class TvPipModule {
ShellInit shellInit, ShellInit shellInit,
ShellController shellController, ShellController shellController,
TvPipBoundsState tvPipBoundsState, TvPipBoundsState tvPipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController, TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener, PipAppOpsListener pipAppOpsListener,
@@ -88,6 +90,7 @@ public abstract class TvPipModule {
shellInit, shellInit,
shellController, shellController,
tvPipBoundsState, tvPipBoundsState,
pipSizeSpecHandler,
tvPipBoundsAlgorithm, tvPipBoundsAlgorithm,
tvPipBoundsController, tvPipBoundsController,
pipAppOpsListener, pipAppOpsListener,
@@ -127,14 +130,23 @@ public abstract class TvPipModule {
@WMSingleton @WMSingleton
@Provides @Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) { TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm); PipSizeSpecHandler pipSizeSpecHandler) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
pipSizeSpecHandler);
} }
@WMSingleton @WMSingleton
@Provides @Provides
static TvPipBoundsState provideTvPipBoundsState(Context context) { static TvPipBoundsState provideTvPipBoundsState(Context context,
return new TvPipBoundsState(context); PipSizeSpecHandler pipSizeSpecHandler) {
return new TvPipBoundsState(context, pipSizeSpecHandler);
}
@WMSingleton
@Provides
static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
return new PipSizeSpecHandler(context);
} }
// Handler needed for loadDrawableAsync() in PipControlsViewController // Handler needed for loadDrawableAsync() in PipControlsViewController

View File

@@ -77,6 +77,7 @@ import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -338,6 +339,7 @@ public abstract class WMShellModule {
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState, PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper, PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PhonePipMenuController phonePipMenuController,
@@ -354,17 +356,18 @@ public abstract class WMShellModule {
return Optional.ofNullable(PipController.create( return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController, context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
pipTransitionController, windowManagerShellWrapper, taskStackListener, pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
pipParamsChangedForwarder, displayInsetsController, oneHandedController, taskStackListener, pipParamsChangedForwarder, displayInsetsController,
mainExecutor)); oneHandedController, mainExecutor));
} }
@WMSingleton @WMSingleton
@Provides @Provides
static PipBoundsState providePipBoundsState(Context context) { static PipBoundsState providePipBoundsState(Context context,
return new PipBoundsState(context); PipSizeSpecHandler pipSizeSpecHandler) {
return new PipBoundsState(context, pipSizeSpecHandler);
} }
@WMSingleton @WMSingleton
@@ -379,13 +382,20 @@ public abstract class WMShellModule {
return new PhonePipKeepClearAlgorithm(context); return new PhonePipKeepClearAlgorithm(context);
} }
@WMSingleton
@Provides
static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
return new PipSizeSpecHandler(context);
}
@WMSingleton @WMSingleton
@Provides @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) { PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipSizeSpecHandler pipSizeSpecHandler) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
pipKeepClearAlgorithm); pipKeepClearAlgorithm, pipSizeSpecHandler);
} }
// Handler is used by Icon.loadDrawableAsync // Handler is used by Icon.loadDrawableAsync
@@ -409,13 +419,14 @@ public abstract class WMShellModule {
PhonePipMenuController menuPhoneController, PhonePipMenuController menuPhoneController,
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer, PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper, PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator, FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger, PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) { @ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, pipTaskOrganizer, pipMotionHelper, pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor); floatingContentCoordinator, pipUiEventLogger, mainExecutor);
} }

View File

@@ -16,24 +16,19 @@
package com.android.wm.shell.pip; package com.android.wm.shell.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.PictureInPictureParams; import android.app.PictureInPictureParams;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Size; import android.util.Size;
import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -45,33 +40,29 @@ public class PipBoundsAlgorithm {
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f; private static final float INVALID_SNAP_FRACTION = -1f;
private final @NonNull PipBoundsState mPipBoundsState; @NonNull private final PipBoundsState mPipBoundsState;
@NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipSnapAlgorithm mSnapAlgorithm; private final PipSnapAlgorithm mSnapAlgorithm;
private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private float mDefaultSizePercent;
private float mMinAspectRatioForMinSize;
private float mMaxAspectRatioForMinSize;
private float mDefaultAspectRatio; private float mDefaultAspectRatio;
private float mMinAspectRatio; private float mMinAspectRatio;
private float mMaxAspectRatio; private float mMaxAspectRatio;
private int mDefaultStackGravity; private int mDefaultStackGravity;
private int mDefaultMinSize;
private int mOverridableMinSize;
protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm, @NonNull PipSnapAlgorithm pipSnapAlgorithm,
@NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) { @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipSizeSpecHandler pipSizeSpecHandler) {
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm; mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipSizeSpecHandler = pipSizeSpecHandler;
reloadResources(context); reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
// triggers a configuration change and the resources to be reloaded. // triggers a configuration change and the resources to be reloaded.
mPipBoundsState.setAspectRatio(mDefaultAspectRatio); mPipBoundsState.setAspectRatio(mDefaultAspectRatio);
mPipBoundsState.setMinEdgeSize(mDefaultMinSize);
} }
/** /**
@@ -83,27 +74,15 @@ public class PipBoundsAlgorithm {
R.dimen.config_pictureInPictureDefaultAspectRatio); R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger( mDefaultStackGravity = res.getInteger(
R.integer.config_defaultPictureInPictureGravity); R.integer.config_defaultPictureInPictureGravity);
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task);
mOverridableMinSize = res.getDimensionPixelSize(
R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString( final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets); R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
? Size.parseSize(screenEdgeInsetsDpString) ? Size.parseSize(screenEdgeInsetsDpString)
: null; : null;
mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
: new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
mMinAspectRatio = res.getFloat( mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat( mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio); com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
mDefaultSizePercent = res.getFloat(
R.dimen.config_pictureInPictureDefaultSizePercent);
mMaxAspectRatioForMinSize = res.getFloat(
R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
} }
/** /**
@@ -180,8 +159,9 @@ public class PipBoundsAlgorithm {
if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
// If either dimension is smaller than the allowed minimum, adjust them // If either dimension is smaller than the allowed minimum, adjust them
// according to mOverridableMinSize // according to mOverridableMinSize
return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize), return new Size(
Math.max(windowLayout.minHeight, mOverridableMinSize)); Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
} }
return null; return null;
} }
@@ -243,28 +223,13 @@ public class PipBoundsAlgorithm {
final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds, final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
getMovementBounds(stackBounds), mPipBoundsState.getStashedState()); getMovementBounds(stackBounds), mPipBoundsState.getStashedState());
final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
final Size size; final Size size;
if (useCurrentMinEdgeSize || useCurrentSize) { if (useCurrentMinEdgeSize || useCurrentSize) {
// The default minimum edge size, or the override min edge size if set. // Use the existing size but adjusted to the new aspect ratio.
final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize size = mPipSizeSpecHandler.getSizeForAspectRatio(
: mPipBoundsState.getOverrideMinEdgeSize(); new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
: defaultMinEdgeSize;
// Use the existing size but adjusted to the aspect ratio and min edge size.
size = getSizeForAspectRatio(
new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
} else { } else {
if (overrideMinSize != null) { size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
// The override minimal size is set, use that as the default size making sure it's
// adjusted to the aspect ratio.
size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio);
} else {
// Calculate the default size using the display size and default min edge size.
final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
displayLayout.width(), displayLayout.height());
}
} }
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -273,18 +238,6 @@ public class PipBoundsAlgorithm {
mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction); mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
} }
/** Adjusts the given size to conform to the given aspect ratio. */
private Size adjustSizeToAspectRatio(@NonNull Size size, float aspectRatio) {
final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
if (sizeAspectRatio > aspectRatio) {
// Size is wider, fix the width and increase the height
return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
} else {
// Size is taller, fix the height and adjust the width.
return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
}
}
/** /**
* @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are * @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
* provided, then it will apply the default bounds to the provided snap fraction and size. * provided, then it will apply the default bounds to the provided snap fraction and size.
@@ -303,17 +256,9 @@ public class PipBoundsAlgorithm {
final Size defaultSize; final Size defaultSize;
final Rect insetBounds = new Rect(); final Rect insetBounds = new Rect();
getInsetBounds(insetBounds); getInsetBounds(insetBounds);
final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
final Size overrideMinSize = mPipBoundsState.getOverrideMinSize(); // Calculate the default size
if (overrideMinSize != null) { defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
// The override minimal size is set, use that as the default size making sure it's
// adjusted to the aspect ratio.
defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
} else {
// Calculate the default size using the display size and default min edge size.
defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
mDefaultMinSize, displayLayout.width(), displayLayout.height());
}
// Now that we have the default size, apply the snap fraction if valid or position the // Now that we have the default size, apply the snap fraction if valid or position the
// bounds using the default gravity. // bounds using the default gravity.
@@ -335,12 +280,7 @@ public class PipBoundsAlgorithm {
* Populates the bounds on the screen that the PIP can be visible in. * Populates the bounds on the screen that the PIP can be visible in.
*/ */
public void getInsetBounds(Rect outRect) { public void getInsetBounds(Rect outRect) {
final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); outRect.set(mPipSizeSpecHandler.getInsetBounds());
Rect insets = mPipBoundsState.getDisplayLayout().stableInsets();
outRect.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
displayLayout.width() - insets.right - mScreenEdgeInsets.x,
displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
} }
/** /**
@@ -405,71 +345,11 @@ public class PipBoundsAlgorithm {
mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction); mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction);
} }
public int getDefaultMinSize() {
return mDefaultMinSize;
}
/** /**
* @return the pixels for a given dp value. * @return the pixels for a given dp value.
*/ */
private int dpToPx(float dpValue, DisplayMetrics dm) { private int dpToPx(float dpValue, DisplayMetrics dm) {
return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); return PipUtils.dpToPx(dpValue, dm);
}
/**
* @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
* is at least minEdgeSize.
*/
public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
int displayHeight) {
final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
final int width;
final int height;
if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
// Beyond these points, we can just use the min size as the shorter edge
if (aspectRatio <= 1) {
// Portrait, width is the minimum size
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
} else {
// Within these points, we ensure that the bounds fit within the radius of the limits
// at the points
final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
height = (int) Math.round(Math.sqrt((radius * radius)
/ (aspectRatio * aspectRatio + 1)));
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
/**
* @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
* minimum edge is at least minEdgeSize.
*/
public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
final int smallestSize = Math.min(size.getWidth(), size.getHeight());
final int minSize = (int) Math.max(minEdgeSize, smallestSize);
final int width;
final int height;
if (aspectRatio <= 1) {
// Portrait, width is the minimum size.
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
} }
/** /**

View File

@@ -37,6 +37,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.TriConsumer; import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -83,13 +84,10 @@ public class PipBoundsState {
private int mStashedState = STASH_TYPE_NONE; private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset; private int mStashOffset;
private @Nullable PipReentryState mPipReentryState; private @Nullable PipReentryState mPipReentryState;
private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName; private @Nullable ComponentName mLastPipComponentName;
private int mDisplayId = Display.DEFAULT_DISPLAY; private int mDisplayId = Display.DEFAULT_DISPLAY;
private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout(); private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
/** The current minimum edge size of PIP. */
private int mMinEdgeSize;
/** The preferred minimum (and default) size specified by apps. */
private @Nullable Size mOverrideMinSize;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing; private boolean mIsImeShowing;
private int mImeHeight; private int mImeHeight;
@@ -122,9 +120,10 @@ public class PipBoundsState {
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
public PipBoundsState(@NonNull Context context) { public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
mContext = context; mContext = context;
reloadResources(); reloadResources();
mPipSizeSpecHandler = pipSizeSpecHandler;
} }
/** Reloads the resources. */ /** Reloads the resources. */
@@ -323,20 +322,10 @@ public class PipBoundsState {
mPipReentryState = null; mPipReentryState = null;
} }
/** Set the PIP minimum edge size. */
public void setMinEdgeSize(int minEdgeSize) {
mMinEdgeSize = minEdgeSize;
}
/** Returns the PIP's current minimum edge size. */
public int getMinEdgeSize() {
return mMinEdgeSize;
}
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) { public void setOverrideMinSize(@Nullable Size overrideMinSize) {
final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize); final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
mOverrideMinSize = overrideMinSize; mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
if (changed && mOnMinimalSizeChangeCallback != null) { if (changed && mOnMinimalSizeChangeCallback != null) {
mOnMinimalSizeChangeCallback.run(); mOnMinimalSizeChangeCallback.run();
} }
@@ -345,13 +334,12 @@ public class PipBoundsState {
/** Returns the preferred minimal size specified by the activity in PIP. */ /** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable @Nullable
public Size getOverrideMinSize() { public Size getOverrideMinSize() {
return mOverrideMinSize; return mPipSizeSpecHandler.getOverrideMinSize();
} }
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */ /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() { public int getOverrideMinEdgeSize() {
if (mOverrideMinSize == null) return 0; return mPipSizeSpecHandler.getOverrideMinEdgeSize();
return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
} }
/** Get the state of the bounds in motion. */ /** Get the state of the bounds in motion. */
@@ -581,11 +569,8 @@ public class PipBoundsState {
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName); pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio); pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
pw.println(innerPrefix + "mStashedState=" + mStashedState); pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset); pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize);
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing); pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.ActivityTaskManager; import android.app.ActivityTaskManager;
@@ -26,8 +27,10 @@ import android.app.RemoteAction;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.TypedValue;
import android.window.TaskSnapshot; import android.window.TaskSnapshot;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
@@ -69,6 +72,13 @@ public class PipUtils {
return new Pair<>(null, 0); return new Pair<>(null, 0);
} }
/**
* @return the pixels for a given dp value.
*/
public static int dpToPx(float dpValue, DisplayMetrics dm) {
return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
}
/** /**
* @return true if the aspect ratios differ * @return true if the aspect ratios differ
*/ */

View File

@@ -137,6 +137,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
private PipMotionHelper mPipMotionHelper; private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler; private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController; private PipTransitionController mPipTransitionController;
@@ -380,6 +381,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState, PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper, PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PhonePipMenuController phonePipMenuController,
@@ -401,11 +403,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController, return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, displayController, pipAnimationController, pipAppOpsListener,
pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper, pipTransitionState, pipTouchHandler, pipTransitionController,
taskStackListener, pipParamsChangedForwarder, displayInsetsController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor) displayInsetsController, oneHandedController, mainExecutor)
.mImpl; .mImpl;
} }
@@ -419,6 +421,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState, @NonNull PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper, PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PhonePipMenuController phonePipMenuController,
@@ -444,6 +447,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
mPipSizeSpecHandler = pipSizeSpecHandler;
mPipMotionHelper = pipMotionHelper; mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer; mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState; mPipTransitionState = pipTransitionState;
@@ -512,7 +516,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Ensure that we have the display info in case we get calls to update the bounds before the // Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back // listener calls back
mPipBoundsState.setDisplayId(mContext.getDisplayId()); mPipBoundsState.setDisplayId(mContext.getDisplayId());
mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
mPipSizeSpecHandler.setDisplayLayout(layout);
mPipBoundsState.setDisplayLayout(layout);
try { try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener); mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -686,6 +693,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.onConfigurationChanged(mContext); mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged(); mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged(); mPipBoundsState.onConfigurationChanged();
mPipSizeSpecHandler.onConfigurationChanged();
} }
@Override @Override
@@ -711,7 +719,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Runnable updateDisplayLayout = () -> { Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
&& mPipBoundsState.getDisplayLayout().rotation() != layout.rotation(); && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
// update the internal state of objects subscribed to display changes
mPipSizeSpecHandler.setDisplayLayout(layout);
mPipBoundsState.setDisplayLayout(layout); mPipBoundsState.setDisplayLayout(layout);
final WindowContainerTransaction wct = final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null; fromRotation ? new WindowContainerTransaction() : null;
updateMovementBounds(null /* toBounds */, updateMovementBounds(null /* toBounds */,
@@ -1083,6 +1095,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.dump(pw, innerPrefix); mPipTaskOrganizer.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix); mPipInputConsumer.dump(pw, innerPrefix);
mPipSizeSpecHandler.dump(pw, innerPrefix);
} }
/** /**

View File

@@ -0,0 +1,536 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.pip.phone;
import static com.android.wm.shell.pip.PipUtils.dpToPx;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
/**
* Acts as a source of truth for appropriate size spec for PIP.
*/
public class PipSizeSpecHandler {
private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
@NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
@VisibleForTesting
final SizeSpecSource mSizeSpecSourceImpl;
/** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
private int mOverridableMinSize;
/** Used to store values obtained from resource files. */
private Point mScreenEdgeInsets;
private float mMinAspectRatioForMinSize;
private float mMaxAspectRatioForMinSize;
private int mDefaultMinSize;
@NonNull private final Context mContext;
private interface SizeSpecSource {
/** Returns max size allowed for the PIP window */
Size getMaxSize(float aspectRatio);
/** Returns default size for the PIP window */
Size getDefaultSize(float aspectRatio);
/** Returns min size allowed for the PIP window */
Size getMinSize(float aspectRatio);
/** Returns the adjusted size based on current size and target aspect ratio */
Size getSizeForAspectRatio(Size size, float aspectRatio);
/** Updates internal resources on configuration changes */
default void reloadResources() {}
}
/**
* Determines PIP window size optimized for large screens and aspect ratios close to 1:1
*/
private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
/** Default and minimum percentages for the PIP size logic. */
private final float mDefaultSizePercent;
private final float mMinimumSizePercent;
/** Aspect ratio that the PIP size spec logic optimizes for. */
private float mOptimizedAspectRatio;
private SizeSpecLargeScreenOptimizedImpl() {
mDefaultSizePercent = Float.parseFloat(SystemProperties
.get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
mMinimumSizePercent = Float.parseFloat(SystemProperties
.get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
}
@Override
public void reloadResources() {
final Resources res = mContext.getResources();
mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
// make sure the optimized aspect ratio is valid with a default value to fall back to
if (mOptimizedAspectRatio > 1) {
mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
}
}
/**
* Calculates the max size of PIP.
*
* Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
* As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
* whole screen. A linear function is used to calculate these sizes.
*
* @param aspectRatio aspect ratio of the PIP window
* @return dimensions of the max size of the PIP
*/
@Override
public Size getMaxSize(float aspectRatio) {
final int totalHorizontalPadding = getInsetBounds().left
+ (getDisplayBounds().width() - getInsetBounds().right);
final int totalVerticalPadding = getInsetBounds().top
+ (getDisplayBounds().height() - getInsetBounds().bottom);
final int shorterLength = (int) (1f * Math.min(
getDisplayBounds().width() - totalHorizontalPadding,
getDisplayBounds().height() - totalVerticalPadding));
int maxWidth, maxHeight;
// use the optimized max sizing logic only within a certain aspect ratio range
if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
// this formula and its derivation is explained in b/198643358#comment16
maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+ shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+ aspectRatio));
maxHeight = (int) (maxWidth / aspectRatio);
} else {
if (aspectRatio > 1f) {
maxWidth = shorterLength;
maxHeight = (int) (maxWidth / aspectRatio);
} else {
maxHeight = shorterLength;
maxWidth = (int) (maxHeight * aspectRatio);
}
}
return new Size(maxWidth, maxHeight);
}
/**
* Decreases the dimensions by a percentage relative to max size to get default size.
*
* @param aspectRatio aspect ratio of the PIP window
* @return dimensions of the default size of the PIP
*/
@Override
public Size getDefaultSize(float aspectRatio) {
Size minSize = this.getMinSize(aspectRatio);
if (mOverrideMinSize != null) {
return minSize;
}
Size maxSize = this.getMaxSize(aspectRatio);
int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
minSize.getWidth());
int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
minSize.getHeight());
return new Size(defaultWidth, defaultHeight);
}
/**
* Decreases the dimensions by a certain percentage relative to max size to get min size.
*
* @param aspectRatio aspect ratio of the PIP window
* @return dimensions of the min size of the PIP
*/
@Override
public Size getMinSize(float aspectRatio) {
// if there is an overridden min size provided, return that
if (mOverrideMinSize != null) {
return adjustOverrideMinSizeToAspectRatio(aspectRatio);
}
Size maxSize = this.getMaxSize(aspectRatio);
int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
// make sure the calculated min size is not smaller than the allowed default min size
if (aspectRatio > 1f) {
minHeight = (int) Math.max(minHeight, mDefaultMinSize);
minWidth = (int) (minHeight * aspectRatio);
} else {
minWidth = (int) Math.max(minWidth, mDefaultMinSize);
minHeight = (int) (minWidth / aspectRatio);
}
return new Size(minWidth, minHeight);
}
/**
* Returns the size for target aspect ratio making sure new size conforms with the rules.
*
* <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
* maintaining the same maximum size to current size ratio.
*
* @param size current size
* @param aspectRatio target aspect ratio
*/
@Override
public Size getSizeForAspectRatio(Size size, float aspectRatio) {
// getting the percentage of the max size that current size takes
float currAspectRatio = (float) size.getWidth() / size.getHeight();
Size currentMaxSize = getMaxSize(currAspectRatio);
float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
// getting the max size for the target aspect ratio
Size updatedMaxSize = getMaxSize(aspectRatio);
int width = (int) (updatedMaxSize.getWidth() * currentPercent);
int height = (int) (updatedMaxSize.getHeight() * currentPercent);
// adjust the dimensions if below allowed min edge size
if (width < getMinEdgeSize() && aspectRatio <= 1) {
width = getMinEdgeSize();
height = (int) (width / aspectRatio);
} else if (height < getMinEdgeSize() && aspectRatio > 1) {
height = getMinEdgeSize();
width = (int) (height * aspectRatio);
}
// reduce the dimensions of the updated size to the calculated percentage
return new Size(width, height);
}
}
private class SizeSpecDefaultImpl implements SizeSpecSource {
private float mDefaultSizePercent;
private float mMinimumSizePercent;
@Override
public void reloadResources() {
final Resources res = mContext.getResources();
mMaxAspectRatioForMinSize = res.getFloat(
R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
}
@Override
public Size getMaxSize(float aspectRatio) {
final int shorterLength = Math.min(getDisplayBounds().width(),
getDisplayBounds().height());
final int totalHorizontalPadding = getInsetBounds().left
+ (getDisplayBounds().width() - getInsetBounds().right);
final int totalVerticalPadding = getInsetBounds().top
+ (getDisplayBounds().height() - getInsetBounds().bottom);
final int maxWidth, maxHeight;
if (aspectRatio > 1f) {
maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
shorterLength - totalHorizontalPadding);
maxHeight = (int) (maxWidth / aspectRatio);
} else {
maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
shorterLength - totalVerticalPadding);
maxWidth = (int) (maxHeight * aspectRatio);
}
return new Size(maxWidth, maxHeight);
}
@Override
public Size getDefaultSize(float aspectRatio) {
if (mOverrideMinSize != null) {
return this.getMinSize(aspectRatio);
}
final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
getDisplayBounds().height());
final int minSize = (int) Math.max(getMinEdgeSize(),
smallestDisplaySize * mDefaultSizePercent);
final int width;
final int height;
if (aspectRatio <= mMinAspectRatioForMinSize
|| aspectRatio > mMaxAspectRatioForMinSize) {
// Beyond these points, we can just use the min size as the shorter edge
if (aspectRatio <= 1) {
// Portrait, width is the minimum size
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
} else {
// Within these points, ensure that the bounds fit within the radius of the limits
// at the points
final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
height = (int) Math.round(Math.sqrt((radius * radius)
/ (aspectRatio * aspectRatio + 1)));
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
@Override
public Size getMinSize(float aspectRatio) {
if (mOverrideMinSize != null) {
return adjustOverrideMinSizeToAspectRatio(aspectRatio);
}
final int shorterLength = Math.min(getDisplayBounds().width(),
getDisplayBounds().height());
final int minWidth, minHeight;
if (aspectRatio > 1f) {
minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
shorterLength * mMinimumSizePercent);
minHeight = (int) (minWidth / aspectRatio);
} else {
minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
shorterLength * mMinimumSizePercent);
minWidth = (int) (minHeight * aspectRatio);
}
return new Size(minWidth, minHeight);
}
@Override
public Size getSizeForAspectRatio(Size size, float aspectRatio) {
final int smallestSize = Math.min(size.getWidth(), size.getHeight());
final int minSize = Math.max(getMinEdgeSize(), smallestSize);
final int width;
final int height;
if (aspectRatio <= 1) {
// Portrait, width is the minimum size.
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
}
public PipSizeSpecHandler(Context context) {
mContext = context;
boolean enablePipSizeLargeScreen = SystemProperties
.getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
// choose between two implementations of size spec logic
if (enablePipSizeLargeScreen) {
mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
} else {
mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
}
reloadResources();
}
/** Reloads the resources */
public void onConfigurationChanged() {
reloadResources();
}
private void reloadResources() {
final Resources res = mContext.getResources();
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task);
mOverridableMinSize = res.getDimensionPixelSize(
R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
? Size.parseSize(screenEdgeInsetsDpString)
: null;
mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
: new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
// update the internal resources of the size spec source's stub
mSizeSpecSourceImpl.reloadResources();
}
/** Returns the display's bounds. */
@NonNull
public Rect getDisplayBounds() {
return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
}
/** Get the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
return mDisplayLayout;
}
/** Update the display layout. */
public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
}
public Point getScreenEdgeInsets() {
return mScreenEdgeInsets;
}
/**
* Returns the inset bounds the PIP window can be visible in.
*/
public Rect getInsetBounds() {
Rect insetBounds = new Rect();
final DisplayLayout displayLayout = getDisplayLayout();
Rect insets = getDisplayLayout().stableInsets();
insetBounds.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
displayLayout.width() - insets.right - mScreenEdgeInsets.x,
displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
return insetBounds;
}
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) {
mOverrideMinSize = overrideMinSize;
}
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
if (mOverrideMinSize != null
&& (mOverrideMinSize.getWidth() < mOverridableMinSize
|| mOverrideMinSize.getHeight() < mOverridableMinSize)) {
return new Size(mOverridableMinSize, mOverridableMinSize);
}
return mOverrideMinSize;
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
if (mOverrideMinSize == null) return 0;
return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
}
public int getMinEdgeSize() {
return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
}
/**
* Returns the size for the max size spec.
*/
public Size getMaxSize(float aspectRatio) {
return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
}
/**
* Returns the size for the default size spec.
*/
public Size getDefaultSize(float aspectRatio) {
return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
}
/**
* Returns the size for the min size spec.
*/
public Size getMinSize(float aspectRatio) {
return mSizeSpecSourceImpl.getMinSize(aspectRatio);
}
/**
* Returns the adjusted size so that it conforms to the given aspectRatio.
*
* @param size current size
* @param aspectRatio target aspect ratio
*/
public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
if (size.equals(mOverrideMinSize)) {
return adjustOverrideMinSizeToAspectRatio(aspectRatio);
}
return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
}
/**
* Returns the adjusted overridden min size if it is set; otherwise, returns null.
*
* <p>Overridden min size needs to be adjusted in its own way while making sure that the target
* aspect ratio is maintained
*
* @param aspectRatio target aspect ratio
*/
@Nullable
@VisibleForTesting
Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
if (mOverrideMinSize == null) {
return null;
}
final Size size = getOverrideMinSize();
final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
if (sizeAspectRatio > aspectRatio) {
// Size is wider, fix the width and increase the height
return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
} else {
// Size is taller, fix the height and adjust the width.
return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
}
}
/** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
}
}

View File

@@ -78,7 +78,8 @@ public class PipTouchHandler {
private boolean mEnableResize; private boolean mEnableResize;
private final Context mContext; private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipBoundsState mPipBoundsState; @NonNull private final PipBoundsState mPipBoundsState;
@NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipUiEventLogger mPipUiEventLogger; private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler; private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer; private final PipTaskOrganizer mPipTaskOrganizer;
@@ -99,7 +100,6 @@ public class PipTouchHandler {
// The reference inset bounds, used to determine the dismiss fraction // The reference inset bounds, used to determine the dismiss fraction
private final Rect mInsetBounds = new Rect(); private final Rect mInsetBounds = new Rect();
private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing // Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds // us to send stale bounds
@@ -120,7 +120,6 @@ public class PipTouchHandler {
private float mSavedSnapFraction = -1f; private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents; private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinDismiss; private boolean mMovementWithinDismiss;
private float mMinimumSizePercent;
// Touch state // Touch state
private final PipTouchState mTouchState; private final PipTouchState mTouchState;
@@ -174,6 +173,7 @@ public class PipTouchHandler {
PhonePipMenuController menuController, PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer, PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper, PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator, FloatingContentCoordinator floatingContentCoordinator,
@@ -184,6 +184,7 @@ public class PipTouchHandler {
mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
mPipSizeSpecHandler = pipSizeSpecHandler;
mPipTaskOrganizer = pipTaskOrganizer; mPipTaskOrganizer = pipTaskOrganizer;
mMenuController = menuController; mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger; mPipUiEventLogger = pipUiEventLogger;
@@ -271,10 +272,7 @@ public class PipTouchHandler {
private void reloadResources() { private void reloadResources() {
final Resources res = mContext.getResources(); final Resources res = mContext.getResources();
mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer); mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
mExpandedShortestEdgeSize = res.getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
mPipDismissTargetHandler.updateMagneticTargetSize(); mPipDismissTargetHandler.updateMagneticTargetSize();
} }
@@ -407,10 +405,7 @@ public class PipTouchHandler {
// Calculate the expanded size // Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height(); float aspectRatio = (float) normalBounds.width() / normalBounds.height();
Point displaySize = new Point(); Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
mContext.getDisplay().getRealSize(displaySize);
Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
mPipBoundsState.setExpandedBounds( mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect(); Rect expandedMovementBounds = new Rect();
@@ -418,7 +413,7 @@ public class PipTouchHandler {
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset); bottomOffset);
updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio); updatePipSizeConstraints(normalBounds, aspectRatio);
// The extra offset does not really affect the movement bounds, but are applied based on the // The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift // current state (ime showing, or shelf offset) when we need to actually shift
@@ -496,14 +491,14 @@ public class PipTouchHandler {
* @param aspectRatio aspect ratio to use for the calculation of min/max size * @param aspectRatio aspect ratio to use for the calculation of min/max size
*/ */
public void updateMinMaxSize(float aspectRatio) { public void updateMinMaxSize(float aspectRatio) {
updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(), updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
aspectRatio); aspectRatio);
} }
private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds, private void updatePipSizeConstraints(Rect normalBounds,
float aspectRatio) { float aspectRatio) {
if (mPipResizeGestureHandler.isUsingPinchToZoom()) { if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio); updatePinchResizeSizeConstraints(aspectRatio);
} else { } else {
mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
@@ -511,26 +506,13 @@ public class PipTouchHandler {
} }
} }
private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds, private void updatePinchResizeSizeConstraints(float aspectRatio) {
float aspectRatio) {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
mPipBoundsState.getDisplayBounds().height());
final int totalHorizontalPadding = insetBounds.left
+ (mPipBoundsState.getDisplayBounds().width() - insetBounds.right);
final int totalVerticalPadding = insetBounds.top
+ (mPipBoundsState.getDisplayBounds().height() - insetBounds.bottom);
final int minWidth, minHeight, maxWidth, maxHeight; final int minWidth, minHeight, maxWidth, maxHeight;
if (aspectRatio > 1f) {
minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent); minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
minHeight = (int) (minWidth / aspectRatio); minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding); maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
maxHeight = (int) (maxWidth / aspectRatio); maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
} else {
minHeight = (int) Math.min(normalBounds.height(), shorterLength * mMinimumSizePercent);
minWidth = (int) (minHeight * aspectRatio);
maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalVerticalPadding);
maxWidth = (int) (maxHeight * aspectRatio);
}
mPipResizeGestureHandler.updateMinSize(minWidth, minHeight); mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight); mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
@@ -1064,11 +1046,6 @@ public class PipTouchHandler {
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged(); mMotionHelper.onMovementBoundsChanged();
boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
mPipBoundsState.setMinEdgeSize(
isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize
: mPipBoundsAlgorithm.getDefaultMinSize());
} }
private Rect getMovementBounds(Rect curBounds) { private Rect getMovementBounds(Rect curBounds) {

View File

@@ -41,6 +41,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,9 +64,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
public TvPipBoundsAlgorithm(Context context, public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState, @NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) { @NonNull PipSnapAlgorithm pipSnapAlgorithm,
@NonNull PipSizeSpecHandler pipSizeSpecHandler) {
super(context, tvPipBoundsState, pipSnapAlgorithm, super(context, tvPipBoundsState, pipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}); new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
this.mTvPipBoundsState = tvPipBoundsState; this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context); reloadResources(context);
@@ -370,7 +372,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize(); expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else { } else {
int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y) int maxHeight = displayLayout.height()
- (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
- pipDecorations.top - pipDecorations.bottom; - pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
@@ -393,7 +396,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize(); expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else { } else {
int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x) int maxWidth = displayLayout.width()
- (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
- pipDecorations.left - pipDecorations.right; - pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) { if (maxWidth > aspectRatioWidth) {

View File

@@ -30,6 +30,7 @@ import android.view.Gravity;
import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -64,8 +65,9 @@ public class TvPipBoundsState extends PipBoundsState {
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE; private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE; private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) { public TvPipBoundsState(@NonNull Context context,
super(context); @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
super(context, pipSizeSpecHandler);
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature( mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE); PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
} }

View File

@@ -50,6 +50,7 @@ import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellController;
@@ -102,6 +103,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final ShellController mShellController; private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState; private final TvPipBoundsState mTvPipBoundsState;
private final PipSizeSpecHandler mPipSizeSpecHandler;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController; private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener; private final PipAppOpsListener mAppOpsListener;
@@ -133,6 +135,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit, ShellInit shellInit,
ShellController shellController, ShellController shellController,
TvPipBoundsState tvPipBoundsState, TvPipBoundsState tvPipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController, TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener, PipAppOpsListener pipAppOpsListener,
@@ -151,6 +154,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
shellInit, shellInit,
shellController, shellController,
tvPipBoundsState, tvPipBoundsState,
pipSizeSpecHandler,
tvPipBoundsAlgorithm, tvPipBoundsAlgorithm,
tvPipBoundsController, tvPipBoundsController,
pipAppOpsListener, pipAppOpsListener,
@@ -171,6 +175,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit, ShellInit shellInit,
ShellController shellController, ShellController shellController,
TvPipBoundsState tvPipBoundsState, TvPipBoundsState tvPipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController, TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener, PipAppOpsListener pipAppOpsListener,
@@ -189,9 +194,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mShellController = shellController; mShellController = shellController;
mDisplayController = displayController; mDisplayController = displayController;
DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayLayout(layout);
mTvPipBoundsState.setDisplayId(context.getDisplayId()); mTvPipBoundsState.setDisplayId(context.getDisplayId());
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); mPipSizeSpecHandler = pipSizeSpecHandler;
mPipSizeSpecHandler.setDisplayLayout(layout);
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController; mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this); mTvPipBoundsController.setListener(this);

View File

@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -57,17 +58,22 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo; private DisplayInfo mDefaultDisplayInfo;
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
initializeMockResources(); initializeMockResources();
mPipBoundsState = new PipBoundsState(mContext); mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
mPipBoundsState.setDisplayLayout( DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true)); new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
mPipBoundsState.setDisplayLayout(layout);
mPipSizeSpecHandler.setDisplayLayout(layout);
} }
private void initializeMockResources() { private void initializeMockResources() {
@@ -120,9 +126,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
@Test @Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() { public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO, final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
mDefaultDisplayInfo.logicalHeight);
mPipBoundsState.setOverrideMinSize(null); mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds(); final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
@@ -296,9 +300,9 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
(MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2 (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
}; };
final Size[] minimalSizes = new Size[] { final Size[] minimalSizes = new Size[] {
new Size((int) (100 * aspectRatios[0]), 100), new Size((int) (200 * aspectRatios[0]), 200),
new Size((int) (100 * aspectRatios[1]), 100), new Size((int) (200 * aspectRatios[1]), 200),
new Size((int) (100 * aspectRatios[2]), 100) new Size((int) (200 * aspectRatios[2]), 200)
}; };
for (int i = 0; i < aspectRatios.length; i++) { for (int i = 0; i < aspectRatios.length; i++) {
final float aspectRatio = aspectRatios[i]; final float aspectRatio = aspectRatios[i];

View File

@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer; import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -57,7 +58,7 @@ public class PipBoundsStateTest extends ShellTestCase {
@Before @Before
public void setUp() { public void setUp() {
mPipBoundsState = new PipBoundsState(mContext); mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
mTestComponentName1 = new ComponentName(mContext, "component1"); mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2"); mTestComponentName2 = new ComponentName(mContext, "component2");
} }
@@ -161,10 +162,10 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test @Test
public void testSetOverrideMinSize_notChanged_callbackNotInvoked() { public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
final Runnable callback = mock(Runnable.class); final Runnable callback = mock(Runnable.class);
mPipBoundsState.setOverrideMinSize(new Size(5, 5)); mPipBoundsState.setOverrideMinSize(new Size(100, 150));
mPipBoundsState.setOnMinimalSizeChangeCallback(callback); mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
mPipBoundsState.setOverrideMinSize(new Size(5, 5)); mPipBoundsState.setOverrideMinSize(new Size(100, 150));
verify(callback, never()).run(); verify(callback, never()).run();
} }
@@ -174,11 +175,11 @@ public class PipBoundsStateTest extends ShellTestCase {
mPipBoundsState.setOverrideMinSize(null); mPipBoundsState.setOverrideMinSize(null);
assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize()); assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
mPipBoundsState.setOverrideMinSize(new Size(5, 10)); mPipBoundsState.setOverrideMinSize(new Size(100, 110));
assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize()); assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
mPipBoundsState.setOverrideMinSize(new Size(15, 10)); mPipBoundsState.setOverrideMinSize(new Size(150, 200));
assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize()); assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
} }
@Test @Test

View File

@@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before; import org.junit.Before;
@@ -86,6 +87,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState; private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
private ComponentName mComponent1; private ComponentName mComponent1;
private ComponentName mComponent2; private ComponentName mComponent2;
@@ -95,10 +97,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1"); mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2"); mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext); mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipTransitionState = new PipTransitionState(); mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor(); mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipTaskOrganizer = new PipTaskOrganizer(mContext,
mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
@@ -253,8 +257,10 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private void preparePipTaskOrg() { private void preparePipTaskOrg() {
final DisplayInfo info = new DisplayInfo(); final DisplayInfo info = new DisplayInfo();
mPipBoundsState.setDisplayLayout(new DisplayLayout(info, DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true)); mContext.getResources(), true, true);
mPipBoundsState.setDisplayLayout(layout);
mPipSizeSpecHandler.setDisplayLayout(layout);
mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
mPipTaskOrganizer.setSurfaceControlTransactionFactory( mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction); MockSurfaceControlHelper::createMockSurfaceControlTransaction);

View File

@@ -65,7 +65,6 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellInit;
@@ -108,6 +107,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipMotionHelper mMockPipMotionHelper; @Mock private PipMotionHelper mMockPipMotionHelper;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper; @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState; @Mock private PipBoundsState mMockPipBoundsState;
@Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
@Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor; @Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController; @Mock private Optional<OneHandedController> mMockOneHandedController;
@@ -130,11 +130,12 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController, mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor); mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
mMockOneHandedController, mMockExecutor);
mShellInit.init(); mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -220,11 +221,12 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController, mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor)); mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
mMockOneHandedController, mMockExecutor));
} }
@Test @Test

View File

@@ -85,15 +85,18 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext); mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm(); final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm = final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {}; new PipKeepClearAlgorithmInterface() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm); mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator); mMockPipTransitionController, mFloatingContentCoordinator);

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.pip.phone;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.testing.AndroidTestingRunner;
import android.util.Size;
import android.view.DisplayInfo;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Unit test against {@link PipSizeSpecHandler} with feature flag on.
*/
@RunWith(AndroidTestingRunner.class)
public class PipSizeSpecHandlerTest extends ShellTestCase {
/** A sample overridden min edge size. */
private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
/** A sample default min edge size */
private static final int DEFAULT_MIN_EDGE_SIZE = 40;
/** Display edge size */
private static final int DISPLAY_EDGE_SIZE = 1000;
/** Default sizing percentage */
private static final float DEFAULT_PERCENT = 0.6f;
/** Minimum sizing percentage */
private static final float MIN_PERCENT = 0.5f;
/** Aspect ratio that the new PIP size spec logic optimizes for. */
private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
/** A map of aspect ratios to be tested to expected sizes */
private static Map<Float, Size> sExpectedMaxSizes;
private static Map<Float, Size> sExpectedDefaultSizes;
private static Map<Float, Size> sExpectedMinSizes;
/** A static mockito session object to mock {@link SystemProperties} */
private static StaticMockitoSession sStaticMockitoSession;
@Mock private Context mContext;
@Mock private Resources mResources;
private PipSizeSpecHandler mPipSizeSpecHandler;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
*/
private static void setUpStaticSystemPropertiesSession() {
sStaticMockitoSession = mockitoSession()
.mockStatic(SystemProperties.class).startMocking();
// make sure the feature flag is on
when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
String property = invocation.getArgument(0);
if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
return Float.toString(DEFAULT_PERCENT);
} else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
return Float.toString(MIN_PERCENT);
}
// throw an exception if illegal arguments are used for these tests
throw new InvalidUseOfMatchersException(
String.format("Argument %s does not match", property)
);
});
}
/**
* Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
*/
private static void initExpectedSizes() {
sExpectedMaxSizes = new HashMap<>();
sExpectedDefaultSizes = new HashMap<>();
sExpectedMinSizes = new HashMap<>();
sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
sExpectedMinSizes.put(16f / 9, new Size(499, 281));
sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
sExpectedMinSizes.put(4f / 3, new Size(445, 334));
sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
sExpectedMinSizes.put(3f / 4, new Size(334, 445));
sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
sExpectedMinSizes.put(9f / 16, new Size(281, 499));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
Function<Float, Size> callback) {
for (Map.Entry<Float, Size> expectedSizesEntry : expectedSizes.entrySet()) {
float aspectRatio = expectedSizesEntry.getKey();
Size expectedSize = expectedSizesEntry.getValue();
Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
}
}
@Before
public void setUp() {
initExpectedSizes();
setUpStaticSystemPropertiesSession();
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
when(mResources.getString(anyInt())).thenReturn("0x0");
when(mResources.getDisplayMetrics())
.thenReturn(getContext().getResources().getDisplayMetrics());
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
// no overridden min edge size by default
mPipSizeSpecHandler.setOverrideMinSize(null);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
// use the parent context (not the mocked one) to obtain the display layout
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
false, false);
mPipSizeSpecHandler.setDisplayLayout(displayLayout);
}
@After
public void cleanUp() {
sStaticMockitoSession.finishMocking();
}
@Test
public void testGetMaxSize() {
forEveryTestCaseCheck(sExpectedMaxSizes,
(aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
}
@Test
public void testGetDefaultSize() {
forEveryTestCaseCheck(sExpectedDefaultSizes,
(aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
}
@Test
public void testGetMinSize() {
forEveryTestCaseCheck(sExpectedMinSizes,
(aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
}
@Test
public void testGetSizeForAspectRatio_noOverrideMinSize() {
// an initial size with 16:9 aspect ratio
Size initSize = new Size(600, 337);
Size expectedSize = new Size(337, 599);
Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
@Test
public void testGetSizeForAspectRatio_withOverrideMinSize() {
// an initial size with a 1:1 aspect ratio
mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
OVERRIDE_MIN_EDGE_SIZE));
// make sure initial size is same as override min size
Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
Size expectedSize = new Size(40, 71);
Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
}

View File

@@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect; import android.graphics.Rect;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper; import android.testing.TestableLooper;
import android.util.Size;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -90,6 +91,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipSnapAlgorithm mPipSnapAlgorithm; private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper; private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler; private PipResizeGestureHandler mPipResizeGestureHandler;
private PipSizeSpecHandler mPipSizeSpecHandler;
private DisplayLayout mDisplayLayout; private DisplayLayout mDisplayLayout;
private Rect mInsetBounds; private Rect mInsetBounds;
@@ -103,16 +105,17 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext); mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}); new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator); mMockPipTransitionController, mFloatingContentCoordinator);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper, mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
// We aren't actually using ShellInit, so just call init directly // We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit(); mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
@@ -122,6 +125,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay()); mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
mPipBoundsState.setDisplayLayout(mDisplayLayout); mPipBoundsState.setDisplayLayout(mDisplayLayout);
mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET, mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET, mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET, mPipBoundsState.getDisplayBounds().right - INSET,
@@ -154,13 +158,17 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds, mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
// getting the expected min and max size
float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds()); assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1)) verify(mPipResizeGestureHandler, times(1))
.updateMinSize(mPipBounds.width(), mPipBounds.height()); .updateMinSize(expectedMinSize.getWidth(), expectedMinSize.getHeight());
verify(mPipResizeGestureHandler, times(1)) verify(mPipResizeGestureHandler, times(1))
.updateMaxSize(shorterLength - 2 * mInsetBounds.left, .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
shorterLength - 2 * mInsetBounds.left);
} }
@Test @Test