Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt 0 → 100644 +202 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.content.Context import android.content.res.Resources import android.graphics.PointF import android.util.Size import com.android.wm.shell.R import com.android.wm.shell.pip.PipDisplayLayoutState class LegacySizeSpecSource( private val context: Context, private val pipDisplayLayoutState: PipDisplayLayoutState ) : SizeSpecSource { private var mDefaultMinSize = 0 /** The absolute minimum an overridden size's edge can be */ private var mOverridableMinSize = 0 /** The preferred minimum (and default minimum) size specified by apps. */ private var mOverrideMinSize: Size? = null private var mDefaultSizePercent = 0f private var mMinimumSizePercent = 0f private var mMaxAspectRatioForMinSize = 0f private var mMinAspectRatioForMinSize = 0f init { reloadResources() } private fun reloadResources() { val res: Resources = context.getResources() mDefaultMinSize = res.getDimensionPixelSize( R.dimen.default_minimal_size_pip_resizable_task) mOverridableMinSize = res.getDimensionPixelSize( R.dimen.overridable_minimal_size_pip_resizable_task) mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent) mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1) mMaxAspectRatioForMinSize = res.getFloat( R.dimen.config_pictureInPictureAspectRatioLimitForMinSize) mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize } override fun onConfigurationChanged() { reloadResources() } override fun getMaxSize(aspectRatio: Float): Size { val insetBounds = pipDisplayLayoutState.insetBounds val shorterLength: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val totalHorizontalPadding: Int = (insetBounds.left + (getDisplayBounds().width() - insetBounds.right)) val totalVerticalPadding: Int = (insetBounds.top + (getDisplayBounds().height() - insetBounds.bottom)) return if (aspectRatio > 1f) { val maxWidth = Math.max(getDefaultSize(aspectRatio).width, shorterLength - totalHorizontalPadding) val maxHeight = (maxWidth / aspectRatio).toInt() Size(maxWidth, maxHeight) } else { val maxHeight = Math.max(getDefaultSize(aspectRatio).height, shorterLength - totalVerticalPadding) val maxWidth = (maxHeight * aspectRatio).toInt() Size(maxWidth, maxHeight) } } override fun getDefaultSize(aspectRatio: Float): Size { if (mOverrideMinSize != null) { return getMinSize(aspectRatio) } val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val minSize = Math.max(getMinEdgeSize().toFloat(), smallestDisplaySize * mDefaultSizePercent).toInt() val width: Int val height: Int 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 val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat()) height = Math.round(Math.sqrt((radius * radius / (aspectRatio * aspectRatio + 1)).toDouble())).toInt() width = Math.round(height * aspectRatio) } return Size(width, height) } override fun getMinSize(aspectRatio: Float): Size { if (mOverrideMinSize != null) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val shorterLength: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val minWidth: Int val minHeight: Int if (aspectRatio > 1f) { minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(), shorterLength * mMinimumSizePercent).toInt() minHeight = (minWidth / aspectRatio).toInt() } else { minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(), shorterLength * mMinimumSizePercent).toInt() minWidth = (minHeight * aspectRatio).toInt() } return Size(minWidth, minHeight) } override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { val smallestSize = Math.min(size.width, size.height) val minSize = Math.max(getMinEdgeSize(), smallestSize) val width: Int val height: Int 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 Size(width, height) } private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ override fun setOverrideMinSize(overrideMinSize: Size?) { mOverrideMinSize = overrideMinSize } /** Returns the preferred minimal size specified by the activity in PIP. */ override fun getOverrideMinSize(): Size? { val overrideMinSize = mOverrideMinSize ?: return null return if (overrideMinSize.width < mOverridableMinSize || overrideMinSize.height < mOverridableMinSize) { Size(mOverridableMinSize, mOverridableMinSize) } else { overrideMinSize } } private fun getMinEdgeSize(): Int { return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() } /** * Returns the adjusted overridden min size if it is set; otherwise, returns null. * * * 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 */ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { val size = getOverrideMinSize() ?: return null val sizeAspectRatio = size.width / size.height.toFloat() return if (sizeAspectRatio > aspectRatio) { // Size is wider, fix the width and increase the height Size(size.width, (size.width / aspectRatio).toInt()) } else { // Size is taller, fix the height and adjust the width. Size((size.height * aspectRatio).toInt(), size.height) } } } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt 0 → 100644 +252 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.content.Context import android.content.res.Resources import android.os.SystemProperties import android.util.Size import com.android.wm.shell.R import com.android.wm.shell.pip.PipDisplayLayoutState import java.io.PrintWriter class PhoneSizeSpecSource( private val context: Context, private val pipDisplayLayoutState: PipDisplayLayoutState ) : SizeSpecSource { private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16 private var mDefaultMinSize = 0 /** The absolute minimum an overridden size's edge can be */ private var mOverridableMinSize = 0 /** The preferred minimum (and default minimum) size specified by apps. */ private var mOverrideMinSize: Size? = null /** Default and minimum percentages for the PIP size logic. */ private val mDefaultSizePercent: Float private val mMinimumSizePercent: Float /** Aspect ratio that the PIP size spec logic optimizes for. */ private var mOptimizedAspectRatio = 0f init { mDefaultSizePercent = SystemProperties .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat() mMinimumSizePercent = SystemProperties .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat() reloadResources() } private fun reloadResources() { val res: Resources = context.getResources() mDefaultMinSize = res.getDimensionPixelSize( R.dimen.default_minimal_size_pip_resizable_task) mOverridableMinSize = res.getDimensionPixelSize( R.dimen.overridable_minimal_size_pip_resizable_task) val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio) // make sure the optimized aspect ratio is valid with a default value to fall back to mOptimizedAspectRatio = if (requestedOptAspRatio > 1) { DEFAULT_OPTIMIZED_ASPECT_RATIO } else { requestedOptAspRatio } } override fun onConfigurationChanged() { reloadResources() } /** * 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 fun getMaxSize(aspectRatio: Float): Size { val insetBounds = pipDisplayLayoutState.insetBounds val displayBounds = pipDisplayLayoutState.displayBounds val totalHorizontalPadding: Int = (insetBounds.left + (displayBounds.width() - insetBounds.right)) val totalVerticalPadding: Int = (insetBounds.top + (displayBounds.height() - insetBounds.bottom)) val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding, displayBounds.height() - totalVerticalPadding) var maxWidth: Int val maxHeight: Int // 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 = Math.round(mOptimizedAspectRatio * shorterLength + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio)) // make sure the max width doesn't go beyond shorter screen length after rounding maxWidth = Math.min(maxWidth, shorterLength) maxHeight = Math.round(maxWidth / aspectRatio) } else { if (aspectRatio > 1f) { maxWidth = shorterLength maxHeight = Math.round(maxWidth / aspectRatio) } else { maxHeight = shorterLength maxWidth = Math.round(maxHeight * aspectRatio) } } return 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 fun getDefaultSize(aspectRatio: Float): Size { val minSize = getMinSize(aspectRatio) if (mOverrideMinSize != null) { return minSize } val maxSize = getMaxSize(aspectRatio) val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent), minSize.width) val defaultHeight = Math.round(defaultWidth / aspectRatio) return 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 fun getMinSize(aspectRatio: Float): Size { // if there is an overridden min size provided, return that if (mOverrideMinSize != null) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val maxSize = getMaxSize(aspectRatio) var minWidth = Math.round(maxSize.width * mMinimumSizePercent) var minHeight = Math.round(maxSize.height * mMinimumSizePercent) // make sure the calculated min size is not smaller than the allowed default min size if (aspectRatio > 1f) { minHeight = Math.max(minHeight, mDefaultMinSize) minWidth = Math.round(minHeight * aspectRatio) } else { minWidth = Math.max(minWidth, mDefaultMinSize) minHeight = Math.round(minWidth / aspectRatio) } return Size(minWidth, minHeight) } /** * Returns the size for target aspect ratio making sure new size conforms with the rules. * * * 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 fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { if (size == mOverrideMinSize) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val currAspectRatio = size.width.toFloat() / size.height // getting the percentage of the max size that current size takes val currentMaxSize = getMaxSize(currAspectRatio) val currentPercent = size.width.toFloat() / currentMaxSize.width // getting the max size for the target aspect ratio val updatedMaxSize = getMaxSize(aspectRatio) var width = Math.round(updatedMaxSize.width * currentPercent) var height = Math.round(updatedMaxSize.height * currentPercent) // adjust the dimensions if below allowed min edge size val minEdgeSize = if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() if (width < minEdgeSize && aspectRatio <= 1) { width = minEdgeSize height = Math.round(width / aspectRatio) } else if (height < minEdgeSize && aspectRatio > 1) { height = minEdgeSize width = Math.round(height * aspectRatio) } // reduce the dimensions of the updated size to the calculated percentage return Size(width, height) } /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ override fun setOverrideMinSize(overrideMinSize: Size?) { mOverrideMinSize = overrideMinSize } /** Returns the preferred minimal size specified by the activity in PIP. */ override fun getOverrideMinSize(): Size? { val overrideMinSize = mOverrideMinSize ?: return null return if (overrideMinSize.width < mOverridableMinSize || overrideMinSize.height < mOverridableMinSize) { Size(mOverridableMinSize, mOverridableMinSize) } else { overrideMinSize } } /** * Returns the adjusted overridden min size if it is set; otherwise, returns null. * * * 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 */ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { val size = getOverrideMinSize() ?: return null val sizeAspectRatio = size.width / size.height.toFloat() return if (sizeAspectRatio > aspectRatio) { // Size is wider, fix the width and increase the height Size(size.width, (size.width / aspectRatio).toInt()) } else { // Size is taller, fix the height and adjust the width. Size((size.height * aspectRatio).toInt(), size.height) } } override fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize) pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize) pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize) pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent) pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent) pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio) } } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.util.Size import java.io.PrintWriter interface SizeSpecSource { /** Returns max size allowed for the PIP window */ fun getMaxSize(aspectRatio: Float): Size /** Returns default size for the PIP window */ fun getDefaultSize(aspectRatio: Float): Size /** Returns min size allowed for the PIP window */ fun getMinSize(aspectRatio: Float): Size /** Returns the adjusted size based on current size and target aspect ratio */ fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size /** Overrides the minimum pip size requested by the app */ fun setOverrideMinSize(overrideMinSize: Size?) /** Returns the minimum pip size requested by the app */ fun getOverrideMinSize(): Size? /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ fun getOverrideMinEdgeSize(): Int { val overrideMinSize = getOverrideMinSize() ?: return 0 return Math.min(overrideMinSize.width, overrideMinSize.height) } fun onConfigurationChanged() {} /** Dumps the internal state of the size spec */ fun dump(pw: PrintWriter, prefix: String) {} } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +16 −17 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.onehanded.OneHandedController; Loading @@ -53,7 +55,6 @@ 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.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; Loading Loading @@ -87,7 +88,6 @@ public abstract class Pip1Module { PipBoundsAlgorithm pipBoundsAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, Loading @@ -110,8 +110,7 @@ public abstract class Pip1Module { context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState, pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, Loading @@ -123,8 +122,8 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipBoundsState providePipBoundsState(Context context, PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton Loading @@ -139,21 +138,14 @@ public abstract class Pip1Module { return new PhonePipKeepClearAlgorithm(context); } @WMSingleton @Provides static PipSizeSpecHandler providePipSizeSpecHelper(Context context, PipDisplayLayoutState pipDisplayLayoutState) { return new PipSizeSpecHandler(context, pipDisplayLayoutState); } @WMSingleton @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipSizeSpecHandler pipSizeSpecHandler) { PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, pipSizeSpecHandler); pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); } // Handler is used by Icon.loadDrawableAsync Loading @@ -177,14 +169,14 @@ public abstract class Pip1Module { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler, SizeSpecSource sizeSpecSource, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor); } Loading Loading @@ -241,6 +233,13 @@ public abstract class Pip1Module { splitScreenOptional); } @WMSingleton @Provides static SizeSpecSource provideSizeSpecSource(Context context, PipDisplayLayoutState pipDisplayLayoutState) { return new PhoneSizeSpecSource(context, pipDisplayLayoutState); } @WMSingleton @Provides static PipAppOpsListener providePipAppOpsListener(Context context, Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +8 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt 0 → 100644 +202 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.content.Context import android.content.res.Resources import android.graphics.PointF import android.util.Size import com.android.wm.shell.R import com.android.wm.shell.pip.PipDisplayLayoutState class LegacySizeSpecSource( private val context: Context, private val pipDisplayLayoutState: PipDisplayLayoutState ) : SizeSpecSource { private var mDefaultMinSize = 0 /** The absolute minimum an overridden size's edge can be */ private var mOverridableMinSize = 0 /** The preferred minimum (and default minimum) size specified by apps. */ private var mOverrideMinSize: Size? = null private var mDefaultSizePercent = 0f private var mMinimumSizePercent = 0f private var mMaxAspectRatioForMinSize = 0f private var mMinAspectRatioForMinSize = 0f init { reloadResources() } private fun reloadResources() { val res: Resources = context.getResources() mDefaultMinSize = res.getDimensionPixelSize( R.dimen.default_minimal_size_pip_resizable_task) mOverridableMinSize = res.getDimensionPixelSize( R.dimen.overridable_minimal_size_pip_resizable_task) mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent) mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1) mMaxAspectRatioForMinSize = res.getFloat( R.dimen.config_pictureInPictureAspectRatioLimitForMinSize) mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize } override fun onConfigurationChanged() { reloadResources() } override fun getMaxSize(aspectRatio: Float): Size { val insetBounds = pipDisplayLayoutState.insetBounds val shorterLength: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val totalHorizontalPadding: Int = (insetBounds.left + (getDisplayBounds().width() - insetBounds.right)) val totalVerticalPadding: Int = (insetBounds.top + (getDisplayBounds().height() - insetBounds.bottom)) return if (aspectRatio > 1f) { val maxWidth = Math.max(getDefaultSize(aspectRatio).width, shorterLength - totalHorizontalPadding) val maxHeight = (maxWidth / aspectRatio).toInt() Size(maxWidth, maxHeight) } else { val maxHeight = Math.max(getDefaultSize(aspectRatio).height, shorterLength - totalVerticalPadding) val maxWidth = (maxHeight * aspectRatio).toInt() Size(maxWidth, maxHeight) } } override fun getDefaultSize(aspectRatio: Float): Size { if (mOverrideMinSize != null) { return getMinSize(aspectRatio) } val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val minSize = Math.max(getMinEdgeSize().toFloat(), smallestDisplaySize * mDefaultSizePercent).toInt() val width: Int val height: Int 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 val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat()) height = Math.round(Math.sqrt((radius * radius / (aspectRatio * aspectRatio + 1)).toDouble())).toInt() width = Math.round(height * aspectRatio) } return Size(width, height) } override fun getMinSize(aspectRatio: Float): Size { if (mOverrideMinSize != null) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val shorterLength: Int = Math.min(getDisplayBounds().width(), getDisplayBounds().height()) val minWidth: Int val minHeight: Int if (aspectRatio > 1f) { minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(), shorterLength * mMinimumSizePercent).toInt() minHeight = (minWidth / aspectRatio).toInt() } else { minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(), shorterLength * mMinimumSizePercent).toInt() minWidth = (minHeight * aspectRatio).toInt() } return Size(minWidth, minHeight) } override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { val smallestSize = Math.min(size.width, size.height) val minSize = Math.max(getMinEdgeSize(), smallestSize) val width: Int val height: Int 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 Size(width, height) } private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ override fun setOverrideMinSize(overrideMinSize: Size?) { mOverrideMinSize = overrideMinSize } /** Returns the preferred minimal size specified by the activity in PIP. */ override fun getOverrideMinSize(): Size? { val overrideMinSize = mOverrideMinSize ?: return null return if (overrideMinSize.width < mOverridableMinSize || overrideMinSize.height < mOverridableMinSize) { Size(mOverridableMinSize, mOverridableMinSize) } else { overrideMinSize } } private fun getMinEdgeSize(): Int { return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() } /** * Returns the adjusted overridden min size if it is set; otherwise, returns null. * * * 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 */ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { val size = getOverrideMinSize() ?: return null val sizeAspectRatio = size.width / size.height.toFloat() return if (sizeAspectRatio > aspectRatio) { // Size is wider, fix the width and increase the height Size(size.width, (size.width / aspectRatio).toInt()) } else { // Size is taller, fix the height and adjust the width. Size((size.height * aspectRatio).toInt(), size.height) } } } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt 0 → 100644 +252 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.content.Context import android.content.res.Resources import android.os.SystemProperties import android.util.Size import com.android.wm.shell.R import com.android.wm.shell.pip.PipDisplayLayoutState import java.io.PrintWriter class PhoneSizeSpecSource( private val context: Context, private val pipDisplayLayoutState: PipDisplayLayoutState ) : SizeSpecSource { private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16 private var mDefaultMinSize = 0 /** The absolute minimum an overridden size's edge can be */ private var mOverridableMinSize = 0 /** The preferred minimum (and default minimum) size specified by apps. */ private var mOverrideMinSize: Size? = null /** Default and minimum percentages for the PIP size logic. */ private val mDefaultSizePercent: Float private val mMinimumSizePercent: Float /** Aspect ratio that the PIP size spec logic optimizes for. */ private var mOptimizedAspectRatio = 0f init { mDefaultSizePercent = SystemProperties .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat() mMinimumSizePercent = SystemProperties .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat() reloadResources() } private fun reloadResources() { val res: Resources = context.getResources() mDefaultMinSize = res.getDimensionPixelSize( R.dimen.default_minimal_size_pip_resizable_task) mOverridableMinSize = res.getDimensionPixelSize( R.dimen.overridable_minimal_size_pip_resizable_task) val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio) // make sure the optimized aspect ratio is valid with a default value to fall back to mOptimizedAspectRatio = if (requestedOptAspRatio > 1) { DEFAULT_OPTIMIZED_ASPECT_RATIO } else { requestedOptAspRatio } } override fun onConfigurationChanged() { reloadResources() } /** * 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 fun getMaxSize(aspectRatio: Float): Size { val insetBounds = pipDisplayLayoutState.insetBounds val displayBounds = pipDisplayLayoutState.displayBounds val totalHorizontalPadding: Int = (insetBounds.left + (displayBounds.width() - insetBounds.right)) val totalVerticalPadding: Int = (insetBounds.top + (displayBounds.height() - insetBounds.bottom)) val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding, displayBounds.height() - totalVerticalPadding) var maxWidth: Int val maxHeight: Int // 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 = Math.round(mOptimizedAspectRatio * shorterLength + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio)) // make sure the max width doesn't go beyond shorter screen length after rounding maxWidth = Math.min(maxWidth, shorterLength) maxHeight = Math.round(maxWidth / aspectRatio) } else { if (aspectRatio > 1f) { maxWidth = shorterLength maxHeight = Math.round(maxWidth / aspectRatio) } else { maxHeight = shorterLength maxWidth = Math.round(maxHeight * aspectRatio) } } return 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 fun getDefaultSize(aspectRatio: Float): Size { val minSize = getMinSize(aspectRatio) if (mOverrideMinSize != null) { return minSize } val maxSize = getMaxSize(aspectRatio) val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent), minSize.width) val defaultHeight = Math.round(defaultWidth / aspectRatio) return 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 fun getMinSize(aspectRatio: Float): Size { // if there is an overridden min size provided, return that if (mOverrideMinSize != null) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val maxSize = getMaxSize(aspectRatio) var minWidth = Math.round(maxSize.width * mMinimumSizePercent) var minHeight = Math.round(maxSize.height * mMinimumSizePercent) // make sure the calculated min size is not smaller than the allowed default min size if (aspectRatio > 1f) { minHeight = Math.max(minHeight, mDefaultMinSize) minWidth = Math.round(minHeight * aspectRatio) } else { minWidth = Math.max(minWidth, mDefaultMinSize) minHeight = Math.round(minWidth / aspectRatio) } return Size(minWidth, minHeight) } /** * Returns the size for target aspect ratio making sure new size conforms with the rules. * * * 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 fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { if (size == mOverrideMinSize) { return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! } val currAspectRatio = size.width.toFloat() / size.height // getting the percentage of the max size that current size takes val currentMaxSize = getMaxSize(currAspectRatio) val currentPercent = size.width.toFloat() / currentMaxSize.width // getting the max size for the target aspect ratio val updatedMaxSize = getMaxSize(aspectRatio) var width = Math.round(updatedMaxSize.width * currentPercent) var height = Math.round(updatedMaxSize.height * currentPercent) // adjust the dimensions if below allowed min edge size val minEdgeSize = if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() if (width < minEdgeSize && aspectRatio <= 1) { width = minEdgeSize height = Math.round(width / aspectRatio) } else if (height < minEdgeSize && aspectRatio > 1) { height = minEdgeSize width = Math.round(height * aspectRatio) } // reduce the dimensions of the updated size to the calculated percentage return Size(width, height) } /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ override fun setOverrideMinSize(overrideMinSize: Size?) { mOverrideMinSize = overrideMinSize } /** Returns the preferred minimal size specified by the activity in PIP. */ override fun getOverrideMinSize(): Size? { val overrideMinSize = mOverrideMinSize ?: return null return if (overrideMinSize.width < mOverridableMinSize || overrideMinSize.height < mOverridableMinSize) { Size(mOverridableMinSize, mOverridableMinSize) } else { overrideMinSize } } /** * Returns the adjusted overridden min size if it is set; otherwise, returns null. * * * 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 */ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { val size = getOverrideMinSize() ?: return null val sizeAspectRatio = size.width / size.height.toFloat() return if (sizeAspectRatio > aspectRatio) { // Size is wider, fix the width and increase the height Size(size.width, (size.width / aspectRatio).toInt()) } else { // Size is taller, fix the height and adjust the width. Size((size.height * aspectRatio).toInt(), size.height) } } override fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize) pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize) pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize) pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent) pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent) pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio) } } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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.common.pip import android.util.Size import java.io.PrintWriter interface SizeSpecSource { /** Returns max size allowed for the PIP window */ fun getMaxSize(aspectRatio: Float): Size /** Returns default size for the PIP window */ fun getDefaultSize(aspectRatio: Float): Size /** Returns min size allowed for the PIP window */ fun getMinSize(aspectRatio: Float): Size /** Returns the adjusted size based on current size and target aspect ratio */ fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size /** Overrides the minimum pip size requested by the app */ fun setOverrideMinSize(overrideMinSize: Size?) /** Returns the minimum pip size requested by the app */ fun getOverrideMinSize(): Size? /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ fun getOverrideMinEdgeSize(): Int { val overrideMinSize = getOverrideMinSize() ?: return 0 return Math.min(overrideMinSize.width, overrideMinSize.height) } fun onConfigurationChanged() {} /** Dumps the internal state of the size spec */ fun dump(pw: PrintWriter, prefix: String) {} } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +16 −17 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.onehanded.OneHandedController; Loading @@ -53,7 +55,6 @@ 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.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; Loading Loading @@ -87,7 +88,6 @@ public abstract class Pip1Module { PipBoundsAlgorithm pipBoundsAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, Loading @@ -110,8 +110,7 @@ public abstract class Pip1Module { context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState, pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, Loading @@ -123,8 +122,8 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipBoundsState providePipBoundsState(Context context, PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton Loading @@ -139,21 +138,14 @@ public abstract class Pip1Module { return new PhonePipKeepClearAlgorithm(context); } @WMSingleton @Provides static PipSizeSpecHandler providePipSizeSpecHelper(Context context, PipDisplayLayoutState pipDisplayLayoutState) { return new PipSizeSpecHandler(context, pipDisplayLayoutState); } @WMSingleton @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipSizeSpecHandler pipSizeSpecHandler) { PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, pipSizeSpecHandler); pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); } // Handler is used by Icon.loadDrawableAsync Loading @@ -177,14 +169,14 @@ public abstract class Pip1Module { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler, SizeSpecSource sizeSpecSource, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor); } Loading Loading @@ -241,6 +233,13 @@ public abstract class Pip1Module { splitScreenOptional); } @WMSingleton @Provides static SizeSpecSource provideSizeSpecSource(Context context, PipDisplayLayoutState pipDisplayLayoutState) { return new PhoneSizeSpecSource(context, pipDisplayLayoutState); } @WMSingleton @Provides static PipAppOpsListener providePipAppOpsListener(Context context, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +8 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes