• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.pip;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.graphics.Rect;
23 import android.testing.AndroidTestingRunner;
24 import android.testing.TestableLooper;
25 import android.testing.TestableResources;
26 import android.util.Size;
27 import android.view.DisplayInfo;
28 import android.view.Gravity;
29 
30 import androidx.test.filters.SmallTest;
31 
32 import com.android.wm.shell.R;
33 import com.android.wm.shell.ShellTestCase;
34 import com.android.wm.shell.common.DisplayLayout;
35 import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
36 
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 /**
42  * Unit tests against {@link PipBoundsAlgorithm}, including but not limited to:
43  * - default/movement bounds
44  * - save/restore PiP position on application lifecycle
45  * - save/restore PiP position on screen rotation
46  */
47 @RunWith(AndroidTestingRunner.class)
48 @SmallTest
49 @TestableLooper.RunWithLooper(setAsMainLooper = true)
50 public class PipBoundsAlgorithmTest extends ShellTestCase {
51     private static final int ROUNDING_ERROR_MARGIN = 16;
52     private static final float ASPECT_RATIO_ERROR_MARGIN = 0.01f;
53     private static final float DEFAULT_ASPECT_RATIO = 1f;
54     private static final float MIN_ASPECT_RATIO = 0.5f;
55     private static final float MAX_ASPECT_RATIO = 2f;
56     private static final int DEFAULT_MIN_EDGE_SIZE = 100;
57 
58     private PipBoundsAlgorithm mPipBoundsAlgorithm;
59     private DisplayInfo mDefaultDisplayInfo;
60     private PipBoundsState mPipBoundsState;
61     private PipSizeSpecHandler mPipSizeSpecHandler;
62 
63 
64     @Before
setUp()65     public void setUp() throws Exception {
66         initializeMockResources();
67         mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
68         mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
69         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
70                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
71                 mPipSizeSpecHandler);
72 
73         DisplayLayout layout =
74                 new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
75         mPipBoundsState.setDisplayLayout(layout);
76         mPipSizeSpecHandler.setDisplayLayout(layout);
77     }
78 
initializeMockResources()79     private void initializeMockResources() {
80         final TestableResources res = mContext.getOrCreateTestableResources();
81         res.addOverride(
82                 R.dimen.config_pictureInPictureDefaultAspectRatio,
83                 DEFAULT_ASPECT_RATIO);
84         res.addOverride(
85                 R.integer.config_defaultPictureInPictureGravity,
86                 Gravity.END | Gravity.BOTTOM);
87         res.addOverride(
88                 R.dimen.default_minimal_size_pip_resizable_task,
89                 DEFAULT_MIN_EDGE_SIZE);
90         res.addOverride(
91                 R.string.config_defaultPictureInPictureScreenEdgeInsets,
92                 "16x16");
93         res.addOverride(
94                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio,
95                 MIN_ASPECT_RATIO);
96         res.addOverride(
97                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio,
98                 MAX_ASPECT_RATIO);
99 
100         mDefaultDisplayInfo = new DisplayInfo();
101         mDefaultDisplayInfo.displayId = 1;
102         mDefaultDisplayInfo.logicalWidth = 1000;
103         mDefaultDisplayInfo.logicalHeight = 1500;
104     }
105 
106     @Test
getDefaultAspectRatio()107     public void getDefaultAspectRatio() {
108         assertEquals("Default aspect ratio matches resources",
109                 DEFAULT_ASPECT_RATIO, mPipBoundsAlgorithm.getDefaultAspectRatio(),
110                 ASPECT_RATIO_ERROR_MARGIN);
111     }
112 
113     @Test
onConfigurationChanged_reloadResources()114     public void onConfigurationChanged_reloadResources() {
115         final float newDefaultAspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
116         final TestableResources res = mContext.getOrCreateTestableResources();
117         res.addOverride(R.dimen.config_pictureInPictureDefaultAspectRatio,
118                 newDefaultAspectRatio);
119 
120         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
121 
122         assertEquals("Default aspect ratio should be reloaded",
123                 mPipBoundsAlgorithm.getDefaultAspectRatio(), newDefaultAspectRatio,
124                 ASPECT_RATIO_ERROR_MARGIN);
125     }
126 
127     @Test
getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio()128     public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
129         final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
130 
131         mPipBoundsState.setOverrideMinSize(null);
132         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
133 
134         assertEquals(defaultSize, new Size(defaultBounds.width(), defaultBounds.height()));
135         assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
136                 ASPECT_RATIO_ERROR_MARGIN);
137     }
138 
139     @Test
getDefaultBounds_widerOverrideMinSize_matchesMinSizeWidthAndDefaultAspectRatio()140     public void getDefaultBounds_widerOverrideMinSize_matchesMinSizeWidthAndDefaultAspectRatio() {
141         overrideDefaultAspectRatio(1.0f);
142         // The min size's aspect ratio is greater than the default aspect ratio.
143         final Size overrideMinSize = new Size(150, 120);
144 
145         mPipBoundsState.setOverrideMinSize(overrideMinSize);
146         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
147 
148         // The default aspect ratio should trump the min size aspect ratio.
149         assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
150                 ASPECT_RATIO_ERROR_MARGIN);
151         // The width of the min size is still used with the default aspect ratio.
152         assertEquals(overrideMinSize.getWidth(), defaultBounds.width());
153     }
154 
155     @Test
getDefaultBounds_tallerOverrideMinSize_matchesMinSizeHeightAndDefaultAspectRatio()156     public void getDefaultBounds_tallerOverrideMinSize_matchesMinSizeHeightAndDefaultAspectRatio() {
157         overrideDefaultAspectRatio(1.0f);
158         // The min size's aspect ratio is greater than the default aspect ratio.
159         final Size overrideMinSize = new Size(120, 150);
160 
161         mPipBoundsState.setOverrideMinSize(overrideMinSize);
162         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
163 
164         // The default aspect ratio should trump the min size aspect ratio.
165         assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
166                 ASPECT_RATIO_ERROR_MARGIN);
167         // The height of the min size is still used with the default aspect ratio.
168         assertEquals(overrideMinSize.getHeight(), defaultBounds.height());
169     }
170 
171     @Test
getDefaultBounds_imeShowing_offsetByImeHeight()172     public void getDefaultBounds_imeShowing_offsetByImeHeight() {
173         final int imeHeight = 30;
174         mPipBoundsState.setImeVisibility(false, 0);
175         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
176 
177         mPipBoundsState.setImeVisibility(true, imeHeight);
178         final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
179 
180         assertEquals(imeHeight, defaultBounds.top - defaultBoundsWithIme.top);
181     }
182 
183     @Test
getDefaultBounds_shelfShowing_offsetByShelfHeight()184     public void getDefaultBounds_shelfShowing_offsetByShelfHeight() {
185         final int shelfHeight = 30;
186         mPipBoundsState.setShelfVisibility(false, 0);
187         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
188 
189         mPipBoundsState.setShelfVisibility(true, shelfHeight);
190         final Rect defaultBoundsWithShelf = mPipBoundsAlgorithm.getDefaultBounds();
191 
192         assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithShelf.top);
193     }
194 
195     @Test
getDefaultBounds_imeAndShelfShowing_offsetByTallest()196     public void getDefaultBounds_imeAndShelfShowing_offsetByTallest() {
197         final int imeHeight = 30;
198         final int shelfHeight = 40;
199         mPipBoundsState.setImeVisibility(false, 0);
200         mPipBoundsState.setShelfVisibility(false, 0);
201         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
202 
203         mPipBoundsState.setImeVisibility(true, imeHeight);
204         mPipBoundsState.setShelfVisibility(true, shelfHeight);
205         final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
206 
207         assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithIme.top);
208     }
209 
210     @Test
getDefaultBounds_boundsAtDefaultGravity()211     public void getDefaultBounds_boundsAtDefaultGravity() {
212         final Rect insetBounds = new Rect();
213         mPipBoundsAlgorithm.getInsetBounds(insetBounds);
214         overrideDefaultStackGravity(Gravity.END | Gravity.BOTTOM);
215 
216         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
217 
218         assertEquals(insetBounds.bottom, defaultBounds.bottom);
219         assertEquals(insetBounds.right, defaultBounds.right);
220     }
221 
222     @Test
getNormalBounds_invalidAspectRatio_returnsDefaultBounds()223     public void getNormalBounds_invalidAspectRatio_returnsDefaultBounds() {
224         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
225 
226         // Set an invalid current aspect ratio.
227         mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO / 2);
228         final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
229 
230         assertEquals(defaultBounds, normalBounds);
231     }
232 
233     @Test
getNormalBounds_validAspectRatio_returnsAdjustedDefaultBounds()234     public void getNormalBounds_validAspectRatio_returnsAdjustedDefaultBounds() {
235         final Rect defaultBoundsAdjustedToAspectRatio = mPipBoundsAlgorithm.getDefaultBounds();
236         mPipBoundsAlgorithm.transformBoundsToAspectRatio(defaultBoundsAdjustedToAspectRatio,
237                 MIN_ASPECT_RATIO, false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
238 
239         // Set a valid current aspect ratio different that the default.
240         mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO);
241         final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
242 
243         assertEquals(defaultBoundsAdjustedToAspectRatio, normalBounds);
244     }
245 
246     @Test
getEntryDestinationBounds_returnBoundsMatchesAspectRatio()247     public void getEntryDestinationBounds_returnBoundsMatchesAspectRatio() {
248         final float[] aspectRatios = new float[] {
249                 (MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
250                 DEFAULT_ASPECT_RATIO,
251                 (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
252         };
253         for (float aspectRatio : aspectRatios) {
254             mPipBoundsState.setAspectRatio(aspectRatio);
255             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
256             final float actualAspectRatio = getRectAspectRatio(destinationBounds);
257             assertEquals("Destination bounds matches the given aspect ratio",
258                     aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
259         }
260     }
261 
262     @Test
getEntryDestinationBounds_invalidAspectRatio_returnsDefaultAspectRatio()263     public void getEntryDestinationBounds_invalidAspectRatio_returnsDefaultAspectRatio() {
264         final float[] invalidAspectRatios = new float[] {
265                 MIN_ASPECT_RATIO / 2,
266                 MAX_ASPECT_RATIO * 2
267         };
268         for (float aspectRatio : invalidAspectRatios) {
269             mPipBoundsState.setAspectRatio(aspectRatio);
270             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
271             final float actualAspectRatio =
272                     destinationBounds.width() / (destinationBounds.height() * 1f);
273             assertEquals("Destination bounds fallbacks to default aspect ratio",
274                     mPipBoundsAlgorithm.getDefaultAspectRatio(), actualAspectRatio,
275                     ASPECT_RATIO_ERROR_MARGIN);
276         }
277     }
278 
279     @Test
getAdjustedDestinationBounds_returnBoundsMatchesAspectRatio()280     public void  getAdjustedDestinationBounds_returnBoundsMatchesAspectRatio() {
281         final float aspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
282         final Rect currentBounds = new Rect(0, 0, 0, 100);
283         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
284 
285         mPipBoundsState.setAspectRatio(aspectRatio);
286         final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
287                 currentBounds, aspectRatio);
288 
289         final float actualAspectRatio =
290                 destinationBounds.width() / (destinationBounds.height() * 1f);
291         assertEquals("Destination bounds matches the given aspect ratio",
292                 aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
293     }
294 
295     @Test
getEntryDestinationBounds_withMinSize_returnMinBounds()296     public void getEntryDestinationBounds_withMinSize_returnMinBounds() {
297         final float[] aspectRatios = new float[] {
298                 (MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
299                 DEFAULT_ASPECT_RATIO,
300                 (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
301         };
302         final Size[] minimalSizes = new Size[] {
303                 new Size((int) (200 * aspectRatios[0]), 200),
304                 new Size((int) (200 * aspectRatios[1]), 200),
305                 new Size((int) (200 * aspectRatios[2]), 200)
306         };
307         for (int i = 0; i < aspectRatios.length; i++) {
308             final float aspectRatio = aspectRatios[i];
309             final Size minimalSize = minimalSizes[i];
310             mPipBoundsState.setAspectRatio(aspectRatio);
311             mPipBoundsState.setOverrideMinSize(minimalSize);
312             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
313             assertTrue("Destination bounds is no smaller than minimal requirement",
314                     (destinationBounds.width() == minimalSize.getWidth()
315                             && destinationBounds.height() >= minimalSize.getHeight())
316                             || (destinationBounds.height() == minimalSize.getHeight()
317                             && destinationBounds.width() >= minimalSize.getWidth()));
318             final float actualAspectRatio =
319                     destinationBounds.width() / (destinationBounds.height() * 1f);
320             assertEquals("Destination bounds matches the given aspect ratio",
321                     aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
322         }
323     }
324 
325     @Test
getAdjustedDestinationBounds_ignoreMinBounds()326     public void getAdjustedDestinationBounds_ignoreMinBounds() {
327         final float aspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
328         final Rect currentBounds = new Rect(0, 0, 0, 100);
329         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
330         final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
331 
332         mPipBoundsState.setAspectRatio(aspectRatio);
333         mPipBoundsState.setOverrideMinSize(minSize);
334         final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
335                 currentBounds, aspectRatio);
336 
337         assertTrue("Destination bounds ignores minimal size",
338                 destinationBounds.width() > minSize.getWidth()
339                         && destinationBounds.height() > minSize.getHeight());
340     }
341 
342     @Test
getEntryDestinationBounds_reentryStateExists_restoreLastSize()343     public void getEntryDestinationBounds_reentryStateExists_restoreLastSize() {
344         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
345         final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
346         reentryBounds.scale(1.25f);
347         final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
348 
349         mPipBoundsState.saveReentryState(
350                 new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
351         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
352 
353         assertEquals(reentryBounds.width(), destinationBounds.width());
354         assertEquals(reentryBounds.height(), destinationBounds.height());
355     }
356 
357     @Test
getEntryDestinationBounds_reentryStateExists_restoreLastPosition()358     public void getEntryDestinationBounds_reentryStateExists_restoreLastPosition() {
359         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
360         final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
361         reentryBounds.offset(0, -100);
362         final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
363 
364         mPipBoundsState.saveReentryState(
365                 new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
366 
367         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
368 
369         assertBoundsInclusionWithMargin("restoreLastPosition", reentryBounds, destinationBounds);
370     }
371 
372     @Test
setShelfHeight_offsetBounds()373     public void setShelfHeight_offsetBounds() {
374         final int shelfHeight = 100;
375         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
376         final Rect oldPosition = mPipBoundsAlgorithm.getEntryDestinationBounds();
377 
378         mPipBoundsState.setShelfVisibility(true, shelfHeight);
379         final Rect newPosition = mPipBoundsAlgorithm.getEntryDestinationBounds();
380 
381         oldPosition.offset(0, -shelfHeight);
382         assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition);
383     }
384 
385     @Test
onImeVisibilityChanged_offsetBounds()386     public void onImeVisibilityChanged_offsetBounds() {
387         final int imeHeight = 100;
388         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
389         final Rect oldPosition = mPipBoundsAlgorithm.getEntryDestinationBounds();
390 
391         mPipBoundsState.setImeVisibility(true, imeHeight);
392         final Rect newPosition = mPipBoundsAlgorithm.getEntryDestinationBounds();
393 
394         oldPosition.offset(0, -imeHeight);
395         assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition);
396     }
397 
398     @Test
getEntryDestinationBounds_noReentryState_useDefaultBounds()399     public void getEntryDestinationBounds_noReentryState_useDefaultBounds() {
400         mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
401         final Rect defaultBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
402 
403         mPipBoundsState.clearReentryState();
404 
405         final Rect actualBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
406 
407         assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
408     }
409 
410     @Test
adjustNormalBoundsToFitMenu_alreadyFits()411     public void adjustNormalBoundsToFitMenu_alreadyFits() {
412         final Rect normalBounds = new Rect(0, 0, 400, 711);
413         final Size minMenuSize = new Size(396, 292);
414         mPipBoundsState.setAspectRatio(
415                 ((float) normalBounds.width()) / ((float) normalBounds.height()));
416 
417         final Rect bounds =
418                 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
419 
420         assertEquals(normalBounds, bounds);
421     }
422 
423     @Test
adjustNormalBoundsToFitMenu_widthTooSmall()424     public void adjustNormalBoundsToFitMenu_widthTooSmall() {
425         final Rect normalBounds = new Rect(0, 0, 297, 528);
426         final Size minMenuSize = new Size(396, 292);
427         mPipBoundsState.setAspectRatio(
428                 ((float) normalBounds.width()) / ((float) normalBounds.height()));
429 
430         final Rect bounds =
431                 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
432 
433         assertEquals(minMenuSize.getWidth(), bounds.width());
434         assertEquals(minMenuSize.getWidth() / mPipBoundsState.getAspectRatio(),
435                 bounds.height(), 0.3f);
436     }
437 
438     @Test
adjustNormalBoundsToFitMenu_heightTooSmall()439     public void adjustNormalBoundsToFitMenu_heightTooSmall() {
440         final Rect normalBounds = new Rect(0, 0, 400, 280);
441         final Size minMenuSize = new Size(396, 292);
442         mPipBoundsState.setAspectRatio(
443                 ((float) normalBounds.width()) / ((float) normalBounds.height()));
444 
445         final Rect bounds =
446                 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
447 
448         assertEquals(minMenuSize.getHeight(), bounds.height());
449         assertEquals(minMenuSize.getHeight() * mPipBoundsState.getAspectRatio(),
450                 bounds.width(), 0.3f);
451     }
452 
453     @Test
adjustNormalBoundsToFitMenu_widthAndHeightTooSmall()454     public void adjustNormalBoundsToFitMenu_widthAndHeightTooSmall() {
455         final Rect normalBounds = new Rect(0, 0, 350, 280);
456         final Size minMenuSize = new Size(396, 292);
457         mPipBoundsState.setAspectRatio(
458                 ((float) normalBounds.width()) / ((float) normalBounds.height()));
459 
460         final Rect bounds =
461                 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
462 
463         assertEquals(minMenuSize.getWidth(), bounds.width());
464         assertEquals(minMenuSize.getWidth() / mPipBoundsState.getAspectRatio(),
465                 bounds.height(), 0.3f);
466     }
467 
overrideDefaultAspectRatio(float aspectRatio)468     private void overrideDefaultAspectRatio(float aspectRatio) {
469         final TestableResources res = mContext.getOrCreateTestableResources();
470         res.addOverride(
471                 R.dimen.config_pictureInPictureDefaultAspectRatio,
472                 aspectRatio);
473         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
474     }
475 
overrideDefaultStackGravity(int stackGravity)476     private void overrideDefaultStackGravity(int stackGravity) {
477         final TestableResources res = mContext.getOrCreateTestableResources();
478         res.addOverride(
479                 R.integer.config_defaultPictureInPictureGravity,
480                 stackGravity);
481         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
482     }
483 
assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual)484     private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
485         final Rect expectedWithMargin = new Rect(expected);
486         expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
487         assertTrue(from + ": expect " + expected
488                 + " contains " + actual
489                 + " with error margin " + ROUNDING_ERROR_MARGIN,
490                 expectedWithMargin.contains(actual));
491     }
492 
getRectAspectRatio(Rect rect)493     private static float getRectAspectRatio(Rect rect) {
494         return rect.width() / (rect.height() * 1f);
495     }
496 }
497