diff options
19 files changed, 930 insertions, 258 deletions
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 774f6c6379b2..76eb0945d990 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -105,6 +105,10 @@ 1.777778 </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. Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT --> <integer name="config_defaultPictureInPictureGravity">0x55</integer> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 8022e9b1cd81..94db878233a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -39,6 +39,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; 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.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsState; @@ -69,6 +70,7 @@ public abstract class TvPipModule { ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, @@ -88,6 +90,7 @@ public abstract class TvPipModule { shellInit, shellController, tvPipBoundsState, + pipSizeSpecHandler, tvPipBoundsAlgorithm, tvPipBoundsController, pipAppOpsListener, @@ -127,14 +130,23 @@ public abstract class TvPipModule { @WMSingleton @Provides static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, - TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) { - return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm); + TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, + PipSizeSpecHandler pipSizeSpecHandler) { + return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm, + pipSizeSpecHandler); } @WMSingleton @Provides - static TvPipBoundsState provideTvPipBoundsState(Context context) { - return new TvPipBoundsState(context); + static TvPipBoundsState provideTvPipBoundsState(Context 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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 512a4ef386bb..1135aa36aa4d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -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.PipController; 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.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -338,6 +339,7 @@ public abstract class WMShellModule { PipBoundsAlgorithm pipBoundsAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, @@ -354,17 +356,18 @@ public abstract class WMShellModule { return Optional.ofNullable(PipController.create( context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, - phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, - pipTransitionController, windowManagerShellWrapper, taskStackListener, - pipParamsChangedForwarder, displayInsetsController, oneHandedController, - mainExecutor)); + pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper, + pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, + pipTouchHandler, pipTransitionController, windowManagerShellWrapper, + taskStackListener, pipParamsChangedForwarder, displayInsetsController, + oneHandedController, mainExecutor)); } @WMSingleton @Provides - static PipBoundsState providePipBoundsState(Context context) { - return new PipBoundsState(context); + static PipBoundsState providePipBoundsState(Context context, + PipSizeSpecHandler pipSizeSpecHandler) { + return new PipBoundsState(context, pipSizeSpecHandler); } @WMSingleton @@ -381,11 +384,18 @@ public abstract class WMShellModule { @WMSingleton @Provides + static PipSizeSpecHandler providePipSizeSpecHelper(Context context) { + return new PipSizeSpecHandler(context); + } + + @WMSingleton + @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) { + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipSizeSpecHandler pipSizeSpecHandler) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, - pipKeepClearAlgorithm); + pipKeepClearAlgorithm, pipSizeSpecHandler); } // Handler is used by Icon.loadDrawableAsync @@ -409,13 +419,14 @@ public abstract class WMShellModule { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, pipTaskOrganizer, pipMotionHelper, + pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index f6d67d858f98..867162be4c6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -16,24 +16,19 @@ package com.android.wm.shell.pip; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PictureInPictureParams; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.util.Size; -import android.util.TypedValue; import android.view.Gravity; 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; @@ -45,33 +40,29 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); 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 PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; - private float mDefaultSizePercent; - private float mMinAspectRatioForMinSize; - private float mMaxAspectRatioForMinSize; private float mDefaultAspectRatio; private float mMinAspectRatio; private float mMaxAspectRatio; private int mDefaultStackGravity; - private int mDefaultMinSize; - private int mOverridableMinSize; - protected Point mScreenEdgeInsets; public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, - @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) { + @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, + @NonNull PipSizeSpecHandler pipSizeSpecHandler) { mPipBoundsState = pipBoundsState; mSnapAlgorithm = pipSnapAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; + mPipSizeSpecHandler = pipSizeSpecHandler; reloadResources(context); // 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 // triggers a configuration change and the resources to be reloaded. mPipBoundsState.setAspectRatio(mDefaultAspectRatio); - mPipBoundsState.setMinEdgeSize(mDefaultMinSize); } /** @@ -83,27 +74,15 @@ public class PipBoundsAlgorithm { R.dimen.config_pictureInPictureDefaultAspectRatio); mDefaultStackGravity = res.getInteger( 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( 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())); mMinAspectRatio = res.getFloat( com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); mMaxAspectRatio = res.getFloat( 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 either dimension is smaller than the allowed minimum, adjust them // according to mOverridableMinSize - return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize), - Math.max(windowLayout.minHeight, mOverridableMinSize)); + return new Size( + Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()), + Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize())); } return null; } @@ -243,28 +223,13 @@ public class PipBoundsAlgorithm { final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds), mPipBoundsState.getStashedState()); - final Size overrideMinSize = mPipBoundsState.getOverrideMinSize(); final Size size; if (useCurrentMinEdgeSize || useCurrentSize) { - // The default minimum edge size, or the override min edge size if set. - final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize - : mPipBoundsState.getOverrideMinEdgeSize(); - 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); + // Use the existing size but adjusted to the new aspect ratio. + size = mPipSizeSpecHandler.getSizeForAspectRatio( + new Size(stackBounds.width(), stackBounds.height()), aspectRatio); } else { - if (overrideMinSize != null) { - // 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()); - } + size = mPipSizeSpecHandler.getDefaultSize(aspectRatio); } final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); @@ -273,18 +238,6 @@ public class PipBoundsAlgorithm { 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 * 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 Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); - final Size overrideMinSize = mPipBoundsState.getOverrideMinSize(); - if (overrideMinSize != null) { - // 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()); - } + + // Calculate the default size + defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio); // Now that we have the default size, apply the snap fraction if valid or position the // 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. */ public void getInsetBounds(Rect outRect) { - final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout(); - 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); + outRect.set(mPipSizeSpecHandler.getInsetBounds()); } /** @@ -405,71 +345,11 @@ public class PipBoundsAlgorithm { mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction); } - public int getDefaultMinSize() { - return mDefaultMinSize; - } - /** * @return the pixels for a given dp value. */ private int dpToPx(float dpValue, DisplayMetrics dm) { - return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, 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); + return PipUtils.dpToPx(dpValue, dm); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index 5376ae372de2..61da10be6b67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -37,6 +37,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -83,13 +84,10 @@ public class PipBoundsState { private int mStashedState = STASH_TYPE_NONE; private int mStashOffset; private @Nullable PipReentryState mPipReentryState; + private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler; private @Nullable ComponentName mLastPipComponentName; private int mDisplayId = Display.DEFAULT_DISPLAY; 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 boolean mIsImeShowing; private int mImeHeight; @@ -122,9 +120,10 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); - public PipBoundsState(@NonNull Context context) { + public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) { mContext = context; reloadResources(); + mPipSizeSpecHandler = pipSizeSpecHandler; } /** Reloads the resources. */ @@ -323,20 +322,10 @@ public class PipBoundsState { 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. */ public void setOverrideMinSize(@Nullable Size overrideMinSize) { - final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize); - mOverrideMinSize = overrideMinSize; + final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize()); + mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize); if (changed && mOnMinimalSizeChangeCallback != null) { mOnMinimalSizeChangeCallback.run(); } @@ -345,13 +334,12 @@ public class PipBoundsState { /** Returns the preferred minimal size specified by the activity in PIP. */ @Nullable public Size getOverrideMinSize() { - return mOverrideMinSize; + return mPipSizeSpecHandler.getOverrideMinSize(); } /** 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(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight()); + return mPipSizeSpecHandler.getOverrideMinEdgeSize(); } /** Get the state of the bounds in motion. */ @@ -581,11 +569,8 @@ public class PipBoundsState { pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName); pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio); pw.println(innerPrefix + "mDisplayId=" + mDisplayId); - pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout); pw.println(innerPrefix + "mStashedState=" + mStashedState); pw.println(innerPrefix + "mStashOffset=" + mStashOffset); - pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize); - pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize); pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java index fa0061982c45..8b98790d3499 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java @@ -18,6 +18,7 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.util.TypedValue.COMPLEX_UNIT_DIP; import android.annotation.Nullable; import android.app.ActivityTaskManager; @@ -26,8 +27,10 @@ import android.app.RemoteAction; import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; +import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; +import android.util.TypedValue; import android.window.TaskSnapshot; import com.android.internal.protolog.common.ProtoLog; @@ -70,6 +73,13 @@ public class PipUtils { } /** + * @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 */ public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 525beb166f7e..d86468a838d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -137,6 +137,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; + private PipSizeSpecHandler mPipSizeSpecHandler; private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; private PipTransitionController mPipTransitionController; @@ -380,6 +381,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, @@ -401,11 +403,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb return new PipController(context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, - pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, - pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, - pipTouchHandler, pipTransitionController, windowManagerShellWrapper, - taskStackListener, pipParamsChangedForwarder, displayInsetsController, - oneHandedController, mainExecutor) + pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, + pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipTransitionState, pipTouchHandler, pipTransitionController, + windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, + displayInsetsController, oneHandedController, mainExecutor) .mImpl; } @@ -419,6 +421,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, @@ -444,6 +447,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipBoundsState = pipBoundsState; + mPipSizeSpecHandler = pipSizeSpecHandler; mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; 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 // listener calls back 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 { mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener); @@ -686,6 +693,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); mPipBoundsState.onConfigurationChanged(); + mPipSizeSpecHandler.onConfigurationChanged(); } @Override @@ -711,7 +719,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb Runnable updateDisplayLayout = () -> { final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation(); + + // update the internal state of objects subscribed to display changes + mPipSizeSpecHandler.setDisplayLayout(layout); mPipBoundsState.setDisplayLayout(layout); + final WindowContainerTransaction wct = fromRotation ? new WindowContainerTransaction() : null; updateMovementBounds(null /* toBounds */, @@ -1083,6 +1095,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix); mPipInputConsumer.dump(pw, innerPrefix); + mPipSizeSpecHandler.dump(pw, innerPrefix); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java new file mode 100644 index 000000000000..a906522b8de2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java @@ -0,0 +1,527 @@ +/* + * 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) size specified by apps. */ + @Nullable private Size mOverrideMinSize; + + /** 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); + + 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() { + 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(mOverrideMinSize.getWidth(), mOverrideMinSize.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 = mOverrideMinSize; + 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); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 850c561c891f..0e8d13d9979d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -78,7 +78,8 @@ public class PipTouchHandler { private boolean mEnableResize; private final Context mContext; 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 PipDismissTargetHandler mPipDismissTargetHandler; private final PipTaskOrganizer mPipTaskOrganizer; @@ -99,7 +100,6 @@ public class PipTouchHandler { // The reference inset bounds, used to determine the dismiss fraction private final Rect mInsetBounds = new Rect(); - private int mExpandedShortestEdgeSize; // Used to workaround an issue where the WM rotation happens before we are notified, allowing // us to send stale bounds @@ -120,7 +120,6 @@ public class PipTouchHandler { private float mSavedSnapFraction = -1f; private boolean mSendingHoverAccessibilityEvents; private boolean mMovementWithinDismiss; - private float mMinimumSizePercent; // Touch state private final PipTouchState mTouchState; @@ -174,6 +173,7 @@ public class PipTouchHandler { PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipSizeSpecHandler pipSizeSpecHandler, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -184,6 +184,7 @@ public class PipTouchHandler { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; + mPipSizeSpecHandler = pipSizeSpecHandler; mPipTaskOrganizer = pipTaskOrganizer; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -271,10 +272,7 @@ public class PipTouchHandler { private void reloadResources() { final Resources res = mContext.getResources(); 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); - mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1); mPipDismissTargetHandler.updateMagneticTargetSize(); } @@ -407,10 +405,7 @@ public class PipTouchHandler { // Calculate the expanded size float aspectRatio = (float) normalBounds.width() / normalBounds.height(); - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio( - aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y); + Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio); mPipBoundsState.setExpandedBounds( new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); Rect expandedMovementBounds = new Rect(); @@ -418,7 +413,7 @@ public class PipTouchHandler { mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, bottomOffset); - updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio); + updatePipSizeConstraints(normalBounds, aspectRatio); // 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 @@ -496,14 +491,14 @@ public class PipTouchHandler { * @param aspectRatio aspect ratio to use for the calculation of min/max size */ public void updateMinMaxSize(float aspectRatio) { - updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(), + updatePipSizeConstraints(mPipBoundsState.getNormalBounds(), aspectRatio); } - private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds, + private void updatePipSizeConstraints(Rect normalBounds, float aspectRatio) { if (mPipResizeGestureHandler.isUsingPinchToZoom()) { - updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio); + updatePinchResizeSizeConstraints(aspectRatio); } else { mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), @@ -511,26 +506,13 @@ public class PipTouchHandler { } } - private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds, - 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); + private void updatePinchResizeSizeConstraints(float aspectRatio) { final int minWidth, minHeight, maxWidth, maxHeight; - if (aspectRatio > 1f) { - minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent); - minHeight = (int) (minWidth / aspectRatio); - maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding); - maxHeight = (int) (maxWidth / aspectRatio); - } 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); - } + + minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth(); + minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight(); + maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth(); + maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight(); mPipResizeGestureHandler.updateMinSize(minWidth, minHeight); mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight); @@ -1064,11 +1046,6 @@ public class PipTouchHandler { mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mMotionHelper.onMovementBoundsChanged(); - - boolean isMenuExpanded = mMenuState == MENU_STATE_FULL; - mPipBoundsState.setMinEdgeSize( - isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize - : mPipBoundsAlgorithm.getDefaultMinSize()); } private Rect getMovementBounds(Rect curBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 1ff77f7d36dc..22feb43ffd62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -41,6 +41,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; 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.protolog.ShellProtoLogGroup; @@ -63,9 +64,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { public TvPipBoundsAlgorithm(Context context, @NonNull TvPipBoundsState tvPipBoundsState, - @NonNull PipSnapAlgorithm pipSnapAlgorithm) { + @NonNull PipSnapAlgorithm pipSnapAlgorithm, + @NonNull PipSizeSpecHandler pipSizeSpecHandler) { super(context, tvPipBoundsState, pipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}); + new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler); this.mTvPipBoundsState = tvPipBoundsState; this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); @@ -370,7 +372,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { - int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y) + int maxHeight = displayLayout.height() + - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y) - pipDecorations.top - pipDecorations.bottom; float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; @@ -393,7 +396,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { - int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x) + int maxWidth = displayLayout.width() + - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x) - pipDecorations.left - pipDecorations.right; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; if (maxWidth > aspectRatioWidth) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index ca22882187d8..4e3ee51326c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -30,6 +30,7 @@ import android.view.Gravity; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,8 +65,9 @@ public class TvPipBoundsState extends PipBoundsState { private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE; private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE; - public TvPipBoundsState(@NonNull Context context) { - super(context); + public TvPipBoundsState(@NonNull Context context, + @NonNull PipSizeSpecHandler pipSizeSpecHandler) { + super(context, pipSizeSpecHandler); mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 4e1b0469eb96..6bc666f074e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -50,6 +50,7 @@ import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; 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.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; @@ -102,6 +103,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final ShellController mShellController; private final TvPipBoundsState mTvPipBoundsState; + private final PipSizeSpecHandler mPipSizeSpecHandler; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsController mTvPipBoundsController; private final PipAppOpsListener mAppOpsListener; @@ -133,6 +135,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, @@ -151,6 +154,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal shellInit, shellController, tvPipBoundsState, + pipSizeSpecHandler, tvPipBoundsAlgorithm, tvPipBoundsController, pipAppOpsListener, @@ -171,6 +175,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, @@ -189,9 +194,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mShellController = shellController; mDisplayController = displayController; + DisplayLayout layout = new DisplayLayout(context, context.getDisplay()); + mTvPipBoundsState = tvPipBoundsState; + mTvPipBoundsState.setDisplayLayout(layout); mTvPipBoundsState.setDisplayId(context.getDisplayId()); - mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); + mPipSizeSpecHandler = pipSizeSpecHandler; + mPipSizeSpecHandler.setDisplayLayout(layout); mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; mTvPipBoundsController = tvPipBoundsController; mTvPipBoundsController.setListener(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index 298d0a624869..3d8cd2e4776d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import org.junit.Before; import org.junit.Test; @@ -57,17 +58,22 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { private PipBoundsAlgorithm mPipBoundsAlgorithm; private DisplayInfo mDefaultDisplayInfo; private PipBoundsState mPipBoundsState; + private PipSizeSpecHandler mPipSizeSpecHandler; @Before public void setUp() throws Exception { initializeMockResources(); - mPipBoundsState = new PipBoundsState(mContext); + mPipSizeSpecHandler = new PipSizeSpecHandler(mContext); + mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, - new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); + new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, + mPipSizeSpecHandler); - mPipBoundsState.setDisplayLayout( - new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true)); + DisplayLayout layout = + new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true); + mPipBoundsState.setDisplayLayout(layout); + mPipSizeSpecHandler.setDisplayLayout(layout); } private void initializeMockResources() { @@ -120,9 +126,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { @Test public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() { - final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO, - DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth, - mDefaultDisplayInfo.logicalHeight); + final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO); mPipBoundsState.setOverrideMinSize(null); final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index 8e30f65cee78..ede5bde9c92c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import org.junit.Before; import org.junit.Test; @@ -57,7 +58,7 @@ public class PipBoundsStateTest extends ShellTestCase { @Before public void setUp() { - mPipBoundsState = new PipBoundsState(mContext); + mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext)); mTestComponentName1 = new ComponentName(mContext, "component1"); mTestComponentName2 = new ComponentName(mContext, "component2"); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 17e7d74c57e0..2da4af80fd18 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; @@ -86,6 +87,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; private PipTransitionState mPipTransitionState; private PipBoundsAlgorithm mPipBoundsAlgorithm; + private PipSizeSpecHandler mPipSizeSpecHandler; private ComponentName mComponent1; private ComponentName mComponent2; @@ -95,10 +97,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mComponent1 = new ComponentName(mContext, "component1"); mComponent2 = new ComponentName(mContext, "component2"); - mPipBoundsState = new PipBoundsState(mContext); + mPipSizeSpecHandler = new PipSizeSpecHandler(mContext); + mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler); mPipTransitionState = new PipTransitionState(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, - new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); + new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, + mPipSizeSpecHandler); mMainExecutor = new TestShellExecutor(); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, @@ -253,8 +257,10 @@ public class PipTaskOrganizerTest extends ShellTestCase { private void preparePipTaskOrg() { final DisplayInfo info = new DisplayInfo(); - mPipBoundsState.setDisplayLayout(new DisplayLayout(info, - mContext.getResources(), true, true)); + DisplayLayout layout = new DisplayLayout(info, + mContext.getResources(), true, true); + mPipBoundsState.setDisplayLayout(layout); + mPipSizeSpecHandler.setDisplayLayout(layout); mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); mPipTaskOrganizer.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 35c09a121a1c..4a68287a4486 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -65,7 +65,6 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; 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.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -108,6 +107,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipMotionHelper mMockPipMotionHelper; @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper; @Mock private PipBoundsState mMockPipBoundsState; + @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler; @Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private ShellExecutor mMockExecutor; @Mock private Optional<OneHandedController> mMockOneHandedController; @@ -130,11 +130,12 @@ public class PipControllerTest extends ShellTestCase { mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, - mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, - mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mMockPipParamsChangedForwarder, - mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor); + mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper, + mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, + mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, + mMockWindowManagerShellWrapper, mMockTaskStackListener, + mMockPipParamsChangedForwarder, mMockDisplayInsetsController, + mMockOneHandedController, mMockExecutor); mShellInit.init(); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); @@ -220,11 +221,12 @@ public class PipControllerTest extends ShellTestCase { assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, - mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, - mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mMockPipParamsChangedForwarder, - mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor)); + mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper, + mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, + mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, + mMockWindowManagerShellWrapper, mMockTaskStackListener, + mMockPipParamsChangedForwarder, mMockDisplayInsetsController, + mMockOneHandedController, mMockExecutor)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index c1993b25030b..c7b9eb3d1074 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -85,15 +85,18 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; + private PipSizeSpecHandler mPipSizeSpecHandler; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPipBoundsState = new PipBoundsState(mContext); + mPipSizeSpecHandler = new PipSizeSpecHandler(mContext); + mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler); final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm(); final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm = new PipKeepClearAlgorithmInterface() {}; final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, - mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm); + mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler); final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java new file mode 100644 index 000000000000..d9ff7d1f1089 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java @@ -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); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 8ad2932b69e4..5c4863ff752f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Size; import androidx.test.filters.SmallTest; @@ -90,6 +91,7 @@ public class PipTouchHandlerTest extends ShellTestCase { private PipSnapAlgorithm mPipSnapAlgorithm; private PipMotionHelper mMotionHelper; private PipResizeGestureHandler mPipResizeGestureHandler; + private PipSizeSpecHandler mPipSizeSpecHandler; private DisplayLayout mDisplayLayout; private Rect mInsetBounds; @@ -103,16 +105,17 @@ public class PipTouchHandlerTest extends ShellTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPipBoundsState = new PipBoundsState(mContext); + mPipSizeSpecHandler = new PipSizeSpecHandler(mContext); + mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler); mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}); + new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler); PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, - mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper, - mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); + mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer, + pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); // We aren't actually using ShellInit, so just call init directly mPipTouchHandler.onInit(); mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); @@ -122,6 +125,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay()); mPipBoundsState.setDisplayLayout(mDisplayLayout); + mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout); mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET, mPipBoundsState.getDisplayBounds().top + INSET, mPipBoundsState.getDisplayBounds().right - INSET, @@ -154,13 +158,17 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds, 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()); verify(mPipResizeGestureHandler, times(1)) - .updateMinSize(mPipBounds.width(), mPipBounds.height()); + .updateMinSize(expectedMinSize.getWidth(), expectedMinSize.getHeight()); verify(mPipResizeGestureHandler, times(1)) - .updateMaxSize(shorterLength - 2 * mInsetBounds.left, - shorterLength - 2 * mInsetBounds.left); + .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight()); } @Test |