• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.SystemProperties
22 import android.util.Size
23 import com.android.wm.shell.R
24 import java.io.PrintWriter
25 
26 class PhoneSizeSpecSource(
27         private val context: Context,
28         private val pipDisplayLayoutState: PipDisplayLayoutState
29 ) : SizeSpecSource {
30     private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16
31 
32     private var mDefaultMinSize = 0
33     /** The absolute minimum an overridden size's edge can be */
34     private var mOverridableMinSize = 0
35     /** The preferred minimum (and default minimum) size specified by apps.  */
36     private var mOverrideMinSize: Size? = null
37 
38 
39     /** Default and minimum percentages for the PIP size logic.  */
40     private val mDefaultSizePercent: Float
41     private val mMinimumSizePercent: Float
42 
43     /** Aspect ratio that the PIP size spec logic optimizes for.  */
44     private var mOptimizedAspectRatio = 0f
45 
46     init {
47         mDefaultSizePercent = SystemProperties
48                 .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
49         mMinimumSizePercent = SystemProperties
50                 .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
51 
52         reloadResources()
53     }
54 
reloadResourcesnull55     private fun reloadResources() {
56         val res: Resources = context.getResources()
57 
58         mDefaultMinSize = res.getDimensionPixelSize(
59                 R.dimen.default_minimal_size_pip_resizable_task)
60         mOverridableMinSize = res.getDimensionPixelSize(
61                 R.dimen.overridable_minimal_size_pip_resizable_task)
62 
63         val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
64         // make sure the optimized aspect ratio is valid with a default value to fall back to
65         mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
66             DEFAULT_OPTIMIZED_ASPECT_RATIO
67         } else {
68             requestedOptAspRatio
69         }
70     }
71 
onConfigurationChangednull72     override fun onConfigurationChanged() {
73         reloadResources()
74     }
75 
76     /**
77      * Calculates the max size of PIP.
78      *
79      * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
80      * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
81      * whole screen. A linear function is used to calculate these sizes.
82      *
83      * @param aspectRatio aspect ratio of the PIP window
84      * @return dimensions of the max size of the PIP
85      */
getMaxSizenull86     override fun getMaxSize(aspectRatio: Float): Size {
87         val insetBounds = pipDisplayLayoutState.insetBounds
88         val displayBounds = pipDisplayLayoutState.displayBounds
89 
90         val totalHorizontalPadding: Int = (insetBounds.left +
91                 (displayBounds.width() - insetBounds.right))
92         val totalVerticalPadding: Int = (insetBounds.top +
93                 (displayBounds.height() - insetBounds.bottom))
94         val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding,
95                 displayBounds.height() - totalVerticalPadding)
96         var maxWidth: Int
97         val maxHeight: Int
98 
99         // use the optimized max sizing logic only within a certain aspect ratio range
100         if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
101             // this formula and its derivation is explained in b/198643358#comment16
102             maxWidth = Math.round(mOptimizedAspectRatio * shorterLength +
103                     shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio))
104             // make sure the max width doesn't go beyond shorter screen length after rounding
105             maxWidth = Math.min(maxWidth, shorterLength)
106             maxHeight = Math.round(maxWidth / aspectRatio)
107         } else {
108             if (aspectRatio > 1f) {
109                 maxWidth = shorterLength
110                 maxHeight = Math.round(maxWidth / aspectRatio)
111             } else {
112                 maxHeight = shorterLength
113                 maxWidth = Math.round(maxHeight * aspectRatio)
114             }
115         }
116         return Size(maxWidth, maxHeight)
117     }
118 
119     /**
120      * Decreases the dimensions by a percentage relative to max size to get default size.
121      *
122      * @param aspectRatio aspect ratio of the PIP window
123      * @return dimensions of the default size of the PIP
124      */
getDefaultSizenull125     override fun getDefaultSize(aspectRatio: Float): Size {
126         val minSize = getMinSize(aspectRatio)
127         if (mOverrideMinSize != null) {
128             return minSize
129         }
130         val maxSize = getMaxSize(aspectRatio)
131         val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
132                 minSize.width)
133         val defaultHeight = Math.round(defaultWidth / aspectRatio)
134         return Size(defaultWidth, defaultHeight)
135     }
136 
137     /**
138      * Decreases the dimensions by a certain percentage relative to max size to get min size.
139      *
140      * @param aspectRatio aspect ratio of the PIP window
141      * @return dimensions of the min size of the PIP
142      */
getMinSizenull143     override fun getMinSize(aspectRatio: Float): Size {
144         // if there is an overridden min size provided, return that
145         if (mOverrideMinSize != null) {
146             return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
147         }
148         val maxSize = getMaxSize(aspectRatio)
149         var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
150         var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
151 
152         // make sure the calculated min size is not smaller than the allowed default min size
153         if (aspectRatio > 1f) {
154             minHeight = Math.max(minHeight, mDefaultMinSize)
155             minWidth = Math.round(minHeight * aspectRatio)
156         } else {
157             minWidth = Math.max(minWidth, mDefaultMinSize)
158             minHeight = Math.round(minWidth / aspectRatio)
159         }
160         return Size(minWidth, minHeight)
161     }
162 
163     /**
164      * Returns the size for target aspect ratio making sure new size conforms with the rules.
165      *
166      *
167      * Recalculates the dimensions such that the target aspect ratio is achieved, while
168      * maintaining the same maximum size to current size ratio.
169      *
170      * @param size current size
171      * @param aspectRatio target aspect ratio
172      */
getSizeForAspectRationull173     override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
174         if (size == mOverrideMinSize) {
175             return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
176         }
177 
178         val currAspectRatio = size.width.toFloat() / size.height
179 
180         // getting the percentage of the max size that current size takes
181         val currentMaxSize = getMaxSize(currAspectRatio)
182         val currentPercent = size.width.toFloat() / currentMaxSize.width
183 
184         // getting the max size for the target aspect ratio
185         val updatedMaxSize = getMaxSize(aspectRatio)
186         var width = Math.round(updatedMaxSize.width * currentPercent)
187         var height = Math.round(updatedMaxSize.height * currentPercent)
188 
189         // adjust the dimensions if below allowed min edge size
190         val minEdgeSize =
191                 if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
192 
193         if (width < minEdgeSize && aspectRatio <= 1) {
194             width = minEdgeSize
195             height = Math.round(width / aspectRatio)
196         } else if (height < minEdgeSize && aspectRatio > 1) {
197             height = minEdgeSize
198             width = Math.round(height * aspectRatio)
199         }
200 
201         // reduce the dimensions of the updated size to the calculated percentage
202         return Size(width, height)
203     }
204 
205     /** Sets the preferred size of PIP as specified by the activity in PIP mode.  */
setOverrideMinSizenull206     override fun setOverrideMinSize(overrideMinSize: Size?) {
207         mOverrideMinSize = overrideMinSize
208     }
209 
210     /** Returns the preferred minimal size specified by the activity in PIP.  */
getOverrideMinSizenull211     override fun getOverrideMinSize(): Size? {
212         val overrideMinSize = mOverrideMinSize ?: return null
213         return if (overrideMinSize.width < mOverridableMinSize ||
214                 overrideMinSize.height < mOverridableMinSize) {
215             Size(mOverridableMinSize, mOverridableMinSize)
216         } else {
217             overrideMinSize
218         }
219     }
220 
221     /**
222      * Returns the adjusted overridden min size if it is set; otherwise, returns null.
223      *
224      *
225      * Overridden min size needs to be adjusted in its own way while making sure that the target
226      * aspect ratio is maintained
227      *
228      * @param aspectRatio target aspect ratio
229      */
adjustOverrideMinSizeToAspectRationull230     private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
231         val size = getOverrideMinSize() ?: return null
232         val sizeAspectRatio = size.width / size.height.toFloat()
233         return if (sizeAspectRatio > aspectRatio) {
234             // Size is wider, fix the width and increase the height
235             Size(size.width, (size.width / aspectRatio).toInt())
236         } else {
237             // Size is taller, fix the height and adjust the width.
238             Size((size.height * aspectRatio).toInt(), size.height)
239         }
240     }
241 
dumpnull242     override fun dump(pw: PrintWriter, prefix: String) {
243         val innerPrefix = "$prefix  "
244         pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
245         pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
246         pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
247         pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
248         pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
249         pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
250     }
251 }