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_restoreLastSize()357 public void getEntryDestinationBounds_reentryStateExists_restoreLastSize() { 358 mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO); 359 final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 360 reentryBounds.scale(1.25f); 361 final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds); 362 363 mPipBoundsState.saveReentryState( 364 new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction); 365 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 366 367 assertEquals(reentryBounds.width(), destinationBounds.width()); 368 assertEquals(reentryBounds.height(), destinationBounds.height()); 369 } 370 371 @Test getEntryDestinationBounds_reentryStateExists_restoreLastPosition()372 public void getEntryDestinationBounds_reentryStateExists_restoreLastPosition() { 373 mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO); 374 final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 375 reentryBounds.offset(0, -100); 376 final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds); 377 378 mPipBoundsState.saveReentryState( 379 new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction); 380 381 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 382 383 assertBoundsInclusionWithMargin("restoreLastPosition", reentryBounds, destinationBounds); 384 } 385 386 @Test setShelfHeight_offsetBounds()387 public void setShelfHeight_offsetBounds() { 388 final int shelfHeight = 100; 389 mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO); 390 final Rect oldPosition = mPipBoundsAlgorithm.getEntryDestinationBounds(); 391 392 mPipBoundsState.setShelfVisibility(true, shelfHeight); 393 final Rect newPosition = mPipBoundsAlgorithm.getEntryDestinationBounds(); 394 395 oldPosition.offset(0, -shelfHeight); 396 assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition); 397 } 398 399 @Test onImeVisibilityChanged_offsetBounds()400 public void onImeVisibilityChanged_offsetBounds() { 401 final int imeHeight = 100; 402 mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO); 403 final Rect oldPosition = mPipBoundsAlgorithm.getEntryDestinationBounds(); 404 405 mPipBoundsState.setImeVisibility(true, imeHeight); 406 final Rect newPosition = mPipBoundsAlgorithm.getEntryDestinationBounds(); 407 408 oldPosition.offset(0, -imeHeight); 409 assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition); 410 } 411 412 @Test getEntryDestinationBounds_noReentryState_useDefaultBounds()413 public void getEntryDestinationBounds_noReentryState_useDefaultBounds() { 414 mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO); 415 final Rect defaultBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 416 417 mPipBoundsState.clearReentryState(); 418 419 final Rect actualBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 420 421 assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds); 422 } 423 424 @Test adjustNormalBoundsToFitMenu_alreadyFits()425 public void adjustNormalBoundsToFitMenu_alreadyFits() { 426 final Rect normalBounds = new Rect(0, 0, 400, 711); 427 final Size minMenuSize = new Size(396, 292); 428 mPipBoundsState.setAspectRatio( 429 ((float) normalBounds.width()) / ((float) normalBounds.height())); 430 431 final Rect bounds = 432 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); 433 434 assertEquals(normalBounds, bounds); 435 } 436 437 @Test adjustNormalBoundsToFitMenu_widthTooSmall()438 public void adjustNormalBoundsToFitMenu_widthTooSmall() { 439 final Rect normalBounds = new Rect(0, 0, 297, 528); 440 final Size minMenuSize = new Size(396, 292); 441 mPipBoundsState.setAspectRatio( 442 ((float) normalBounds.width()) / ((float) normalBounds.height())); 443 444 final Rect bounds = 445 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); 446 447 assertEquals(minMenuSize.getWidth(), bounds.width()); 448 assertEquals(minMenuSize.getWidth() / mPipBoundsState.getAspectRatio(), 449 bounds.height(), 0.3f); 450 } 451 452 @Test adjustNormalBoundsToFitMenu_heightTooSmall()453 public void adjustNormalBoundsToFitMenu_heightTooSmall() { 454 final Rect normalBounds = new Rect(0, 0, 400, 280); 455 final Size minMenuSize = new Size(396, 292); 456 mPipBoundsState.setAspectRatio( 457 ((float) normalBounds.width()) / ((float) normalBounds.height())); 458 459 final Rect bounds = 460 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); 461 462 assertEquals(minMenuSize.getHeight(), bounds.height()); 463 assertEquals(minMenuSize.getHeight() * mPipBoundsState.getAspectRatio(), 464 bounds.width(), 0.3f); 465 } 466 467 @Test adjustNormalBoundsToFitMenu_widthAndHeightTooSmall()468 public void adjustNormalBoundsToFitMenu_widthAndHeightTooSmall() { 469 final Rect normalBounds = new Rect(0, 0, 350, 280); 470 final Size minMenuSize = new Size(396, 292); 471 mPipBoundsState.setAspectRatio( 472 ((float) normalBounds.width()) / ((float) normalBounds.height())); 473 474 final Rect bounds = 475 mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); 476 477 assertEquals(minMenuSize.getWidth(), bounds.width()); 478 assertEquals(minMenuSize.getWidth() / mPipBoundsState.getAspectRatio(), 479 bounds.height(), 0.3f); 480 } 481 overrideDefaultAspectRatio(float aspectRatio)482 private void overrideDefaultAspectRatio(float aspectRatio) { 483 final TestableResources res = mContext.getOrCreateTestableResources(); 484 res.addOverride( 485 R.dimen.config_pictureInPictureDefaultAspectRatio, 486 aspectRatio); 487 mPipBoundsAlgorithm.onConfigurationChanged(mContext); 488 } 489 overrideDefaultStackGravity(int stackGravity)490 private void overrideDefaultStackGravity(int stackGravity) { 491 final TestableResources res = mContext.getOrCreateTestableResources(); 492 res.addOverride( 493 R.integer.config_defaultPictureInPictureGravity, 494 stackGravity); 495 mPipBoundsAlgorithm.onConfigurationChanged(mContext); 496 } 497 assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual)498 private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) { 499 final Rect expectedWithMargin = new Rect(expected); 500 expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); 501 assertTrue(from + ": expect " + expected 502 + " contains " + actual 503 + " with error margin " + ROUNDING_ERROR_MARGIN, 504 expectedWithMargin.contains(actual)); 505 } 506 getRectAspectRatio(Rect rect)507 private static float getRectAspectRatio(Rect rect) { 508 return rect.width() / (rect.height() * 1f); 509 } 510 } 511