1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.common.pip 18 19 import android.content.Context 20 import android.content.res.Resources 21 import android.graphics.PointF 22 import android.util.Size 23 import com.android.wm.shell.R 24 25 class LegacySizeSpecSource( 26 private val context: Context, 27 private val pipDisplayLayoutState: PipDisplayLayoutState 28 ) : SizeSpecSource { 29 30 private var mDefaultMinSize = 0 31 /** The absolute minimum an overridden size's edge can be */ 32 private var mOverridableMinSize = 0 33 /** The preferred minimum (and default minimum) size specified by apps. */ 34 private var mOverrideMinSize: Size? = null 35 36 private var mDefaultSizePercent = 0f 37 private var mMinimumSizePercent = 0f 38 private var mMaxAspectRatioForMinSize = 0f 39 private var mMinAspectRatioForMinSize = 0f 40 41 init { 42 reloadResources() 43 } 44 45 private fun reloadResources() { 46 val res: Resources = context.getResources() 47 48 mDefaultMinSize = res.getDimensionPixelSize( 49 R.dimen.default_minimal_size_pip_resizable_task) 50 mOverridableMinSize = res.getDimensionPixelSize( 51 R.dimen.overridable_minimal_size_pip_resizable_task) 52 53 mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent) 54 mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1) 55 56 mMaxAspectRatioForMinSize = res.getFloat( 57 R.dimen.config_pictureInPictureAspectRatioLimitForMinSize) 58 mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize 59 } 60 61 override fun onConfigurationChanged() { 62 reloadResources() 63 } 64 65 override fun getMaxSize(aspectRatio: Float): Size { 66 val insetBounds = pipDisplayLayoutState.insetBounds 67 68 val shorterLength: Int = Math.min(getDisplayBounds().width(), 69 getDisplayBounds().height()) 70 val totalHorizontalPadding: Int = (insetBounds.left + 71 (getDisplayBounds().width() - insetBounds.right)) 72 val totalVerticalPadding: Int = (insetBounds.top + 73 (getDisplayBounds().height() - insetBounds.bottom)) 74 75 return if (aspectRatio > 1f) { 76 val maxWidth = Math.max(getDefaultSize(aspectRatio).width, 77 shorterLength - totalHorizontalPadding) 78 val maxHeight = (maxWidth / aspectRatio).toInt() 79 Size(maxWidth, maxHeight) 80 } else { 81 val maxHeight = Math.max(getDefaultSize(aspectRatio).height, 82 shorterLength - totalVerticalPadding) 83 val maxWidth = (maxHeight * aspectRatio).toInt() 84 Size(maxWidth, maxHeight) 85 } 86 } 87 88 override fun getDefaultSize(aspectRatio: Float): Size { 89 if (mOverrideMinSize != null) { 90 return getMinSize(aspectRatio) 91 } 92 val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(), 93 getDisplayBounds().height()) 94 val minSize = Math.max(getMinEdgeSize().toFloat(), 95 smallestDisplaySize * mDefaultSizePercent).toInt() 96 val width: Int 97 val height: Int 98 if (aspectRatio <= mMinAspectRatioForMinSize || 99 aspectRatio > mMaxAspectRatioForMinSize) { 100 // Beyond these points, we can just use the min size as the shorter edge 101 if (aspectRatio <= 1) { 102 // Portrait, width is the minimum size 103 width = minSize 104 height = Math.round(width / aspectRatio) 105 } else { 106 // Landscape, height is the minimum size 107 height = minSize 108 width = Math.round(height * aspectRatio) 109 } 110 } else { 111 // Within these points, ensure that the bounds fit within the radius of the limits 112 // at the points 113 val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize 114 val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat()) 115 height = Math.round(Math.sqrt((radius * radius / 116 (aspectRatio * aspectRatio + 1)).toDouble())).toInt() 117 width = Math.round(height * aspectRatio) 118 } 119 return Size(width, height) 120 } 121 122 override fun getMinSize(aspectRatio: Float): Size { 123 if (mOverrideMinSize != null) { 124 return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! 125 } 126 val shorterLength: Int = Math.min(getDisplayBounds().width(), 127 getDisplayBounds().height()) 128 val minWidth: Int 129 val minHeight: Int 130 if (aspectRatio > 1f) { 131 minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(), 132 shorterLength * mMinimumSizePercent).toInt() 133 minHeight = (minWidth / aspectRatio).toInt() 134 } else { 135 minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(), 136 shorterLength * mMinimumSizePercent).toInt() 137 minWidth = (minHeight * aspectRatio).toInt() 138 } 139 return Size(minWidth, minHeight) 140 } 141 142 override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { 143 val smallestSize = Math.min(size.width, size.height) 144 val minSize = Math.max(getMinEdgeSize(), smallestSize) 145 val width: Int 146 val height: Int 147 if (aspectRatio <= 1) { 148 // Portrait, width is the minimum size. 149 width = minSize 150 height = Math.round(width / aspectRatio) 151 } else { 152 // Landscape, height is the minimum size 153 height = minSize 154 width = Math.round(height * aspectRatio) 155 } 156 return Size(width, height) 157 } 158 159 private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds 160 161 /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ 162 override fun setOverrideMinSize(overrideMinSize: Size?) { 163 mOverrideMinSize = overrideMinSize 164 } 165 166 /** Returns the preferred minimal size specified by the activity in PIP. */ 167 override fun getOverrideMinSize(): Size? { 168 val overrideMinSize = mOverrideMinSize ?: return null 169 return if (overrideMinSize.width < mOverridableMinSize || 170 overrideMinSize.height < mOverridableMinSize) { 171 Size(mOverridableMinSize, mOverridableMinSize) 172 } else { 173 overrideMinSize 174 } 175 } 176 177 private fun getMinEdgeSize(): Int { 178 return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() 179 } 180 181 /** 182 * Returns the adjusted overridden min size if it is set; otherwise, returns null. 183 * 184 * 185 * Overridden min size needs to be adjusted in its own way while making sure that the target 186 * aspect ratio is maintained 187 * 188 * @param aspectRatio target aspect ratio 189 */ 190 private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { 191 val size = getOverrideMinSize() ?: return null 192 val sizeAspectRatio = size.width / size.height.toFloat() 193 return if (sizeAspectRatio > aspectRatio) { 194 // Size is wider, fix the width and increase the height 195 Size(size.width, (size.width / aspectRatio).toInt()) 196 } else { 197 // Size is taller, fix the height and adjust the width. 198 Size((size.height * aspectRatio).toInt(), size.height) 199 } 200 } 201 }