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