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 package com.android.wm.shell.bubbles 17 18 import android.content.Context 19 import android.content.Intent 20 import android.content.pm.ShortcutInfo 21 import android.content.res.Resources 22 import android.graphics.Insets 23 import android.graphics.PointF 24 import android.graphics.Rect 25 import android.os.UserHandle 26 import android.view.WindowManager 27 import androidx.test.core.app.ApplicationProvider 28 import androidx.test.ext.junit.runners.AndroidJUnit4 29 import androidx.test.filters.SmallTest 30 import com.android.internal.protolog.common.ProtoLog 31 import com.android.wm.shell.R 32 import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT 33 import com.android.wm.shell.common.bubbles.BubbleBarLocation 34 import com.google.common.truth.Truth.assertThat 35 import com.google.common.util.concurrent.MoreExecutors.directExecutor 36 import org.junit.Before 37 import org.junit.Test 38 import org.junit.runner.RunWith 39 40 /** Tests operations and the resulting state managed by [BubblePositioner]. */ 41 @SmallTest 42 @RunWith(AndroidJUnit4::class) 43 class BubblePositionerTest { 44 45 private lateinit var positioner: BubblePositioner 46 private val context = ApplicationProvider.getApplicationContext<Context>() 47 private val resources: Resources 48 get() = context.resources 49 50 private val defaultDeviceConfig = 51 DeviceConfig( 52 windowBounds = Rect(0, 0, 1000, 2000), 53 isLargeScreen = false, 54 isSmallTablet = false, 55 isLandscape = false, 56 isRtl = false, 57 insets = Insets.of(0, 0, 0, 0) 58 ) 59 60 @Before setUpnull61 fun setUp() { 62 ProtoLog.REQUIRE_PROTOLOGTOOL = false 63 val windowManager = context.getSystemService(WindowManager::class.java) 64 positioner = BubblePositioner(context, windowManager) 65 } 66 67 @Test testUpdatenull68 fun testUpdate() { 69 val insets = Insets.of(10, 20, 5, 15) 70 val screenBounds = Rect(0, 0, 1000, 1200) 71 val availableRect = Rect(screenBounds) 72 availableRect.inset(insets) 73 positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds)) 74 assertThat(positioner.availableRect).isEqualTo(availableRect) 75 assertThat(positioner.isLandscape).isFalse() 76 assertThat(positioner.isLargeScreen).isFalse() 77 assertThat(positioner.insets).isEqualTo(insets) 78 } 79 80 @Test testShowBubblesVertically_phonePortraitnull81 fun testShowBubblesVertically_phonePortrait() { 82 positioner.update(defaultDeviceConfig) 83 assertThat(positioner.showBubblesVertically()).isFalse() 84 } 85 86 @Test testShowBubblesVertically_phoneLandscapenull87 fun testShowBubblesVertically_phoneLandscape() { 88 positioner.update(defaultDeviceConfig.copy(isLandscape = true)) 89 assertThat(positioner.isLandscape).isTrue() 90 assertThat(positioner.showBubblesVertically()).isTrue() 91 } 92 93 @Test testShowBubblesVertically_tabletnull94 fun testShowBubblesVertically_tablet() { 95 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) 96 assertThat(positioner.showBubblesVertically()).isTrue() 97 } 98 99 /** If a resting position hasn't been set, calling it will return the default position. */ 100 @Test testGetRestingPosition_returnsDefaultPositionnull101 fun testGetRestingPosition_returnsDefaultPosition() { 102 positioner.update(defaultDeviceConfig) 103 val restingPosition = positioner.getRestingPosition() 104 val defaultPosition = positioner.defaultStartPosition 105 assertThat(restingPosition).isEqualTo(defaultPosition) 106 } 107 108 /** If a resting position has been set, it'll return that instead of the default position. */ 109 @Test testGetRestingPosition_returnsRestingPositionnull110 fun testGetRestingPosition_returnsRestingPosition() { 111 positioner.update(defaultDeviceConfig) 112 val restingPosition = PointF(100f, 100f) 113 positioner.restingPosition = restingPosition 114 assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition) 115 } 116 117 /** Test that the default resting position on phone is in upper left. */ 118 @Test testGetRestingPosition_bubble_onPhonenull119 fun testGetRestingPosition_bubble_onPhone() { 120 positioner.update(defaultDeviceConfig) 121 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 122 val restingPosition = positioner.getRestingPosition() 123 assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left) 124 assertThat(restingPosition.y).isEqualTo(defaultYPosition) 125 } 126 127 @Test testGetRestingPosition_bubble_onPhone_RTLnull128 fun testGetRestingPosition_bubble_onPhone_RTL() { 129 positioner.update(defaultDeviceConfig.copy(isRtl = true)) 130 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 131 val restingPosition = positioner.getRestingPosition() 132 assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right) 133 assertThat(restingPosition.y).isEqualTo(defaultYPosition) 134 } 135 136 /** Test that the default resting position on tablet is middle left. */ 137 @Test testGetRestingPosition_chatBubble_onTabletnull138 fun testGetRestingPosition_chatBubble_onTablet() { 139 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) 140 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 141 val restingPosition = positioner.getRestingPosition() 142 assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left) 143 assertThat(restingPosition.y).isEqualTo(defaultYPosition) 144 } 145 146 @Test testGetRestingPosition_chatBubble_onTablet_RTLnull147 fun testGetRestingPosition_chatBubble_onTablet_RTL() { 148 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) 149 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 150 val restingPosition = positioner.getRestingPosition() 151 assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right) 152 assertThat(restingPosition.y).isEqualTo(defaultYPosition) 153 } 154 155 /** Test that the default resting position on tablet is middle right. */ 156 @Test testGetDefaultPosition_appBubble_onTabletnull157 fun testGetDefaultPosition_appBubble_onTablet() { 158 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) 159 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 160 val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */) 161 assertThat(startPosition.x).isEqualTo(allowableStackRegion.right) 162 assertThat(startPosition.y).isEqualTo(defaultYPosition) 163 } 164 165 @Test testGetRestingPosition_appBubble_onTablet_RTLnull166 fun testGetRestingPosition_appBubble_onTablet_RTL() { 167 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) 168 val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 169 val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */) 170 assertThat(startPosition.x).isEqualTo(allowableStackRegion.left) 171 assertThat(startPosition.y).isEqualTo(defaultYPosition) 172 } 173 174 @Test testGetRestingPosition_afterBoundsChangenull175 fun testGetRestingPosition_afterBoundsChange() { 176 positioner.update( 177 defaultDeviceConfig.copy(isLargeScreen = true, windowBounds = Rect(0, 0, 2000, 1600)) 178 ) 179 180 // Set the resting position to the right side 181 var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 182 val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY()) 183 positioner.restingPosition = restingPosition 184 185 // Now make the device smaller 186 positioner.update( 187 defaultDeviceConfig.copy(isLargeScreen = false, windowBounds = Rect(0, 0, 1000, 1600)) 188 ) 189 190 // Check the resting position is on the correct side 191 allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 192 assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right) 193 } 194 195 @Test testHasUserModifiedDefaultPosition_falsenull196 fun testHasUserModifiedDefaultPosition_false() { 197 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) 198 assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() 199 positioner.restingPosition = positioner.defaultStartPosition 200 assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() 201 } 202 203 @Test testHasUserModifiedDefaultPosition_truenull204 fun testHasUserModifiedDefaultPosition_true() { 205 positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) 206 assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() 207 positioner.restingPosition = PointF(0f, 100f) 208 assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue() 209 } 210 211 @Test testBubbleBarExpandedViewHeightAndWidthnull212 fun testBubbleBarExpandedViewHeightAndWidth() { 213 val deviceConfig = 214 defaultDeviceConfig.copy( 215 // portrait orientation 216 isLandscape = false, 217 isLargeScreen = true, 218 insets = Insets.of(10, 20, 5, 15), 219 windowBounds = Rect(0, 0, 1800, 2600) 220 ) 221 222 positioner.setShowingInBubbleBar(true) 223 positioner.update(deviceConfig) 224 positioner.bubbleBarTopOnScreen = 2500 225 226 val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680 227 val expandedViewVerticalSpacing = 228 resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) 229 val expectedHeight = 230 spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing 231 val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width) 232 233 assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) 234 assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) 235 } 236 237 @Test testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmallnull238 fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() { 239 val screenWidth = 300 240 val deviceConfig = 241 defaultDeviceConfig.copy( 242 // portrait orientation 243 isLandscape = false, 244 isLargeScreen = true, 245 insets = Insets.of(10, 20, 5, 15), 246 windowBounds = Rect(0, 0, screenWidth, 2600) 247 ) 248 positioner.setShowingInBubbleBar(true) 249 positioner.update(deviceConfig) 250 positioner.bubbleBarTopOnScreen = 2500 251 252 val spaceBetweenTopInsetAndBubbleBarInLandscape = 180 253 val expandedViewSpacing = 254 resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) 255 val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing 256 val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing 257 assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) 258 assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) 259 } 260 261 @Test testGetExpandedViewHeight_maxnull262 fun testGetExpandedViewHeight_max() { 263 val deviceConfig = 264 defaultDeviceConfig.copy( 265 isLargeScreen = true, 266 insets = Insets.of(10, 20, 5, 15), 267 windowBounds = Rect(0, 0, 1800, 2600) 268 ) 269 positioner.update(deviceConfig) 270 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 271 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 272 273 assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT) 274 } 275 276 @Test testGetExpandedViewHeight_customHeight_validnull277 fun testGetExpandedViewHeight_customHeight_valid() { 278 val deviceConfig = 279 defaultDeviceConfig.copy( 280 isLargeScreen = true, 281 insets = Insets.of(10, 20, 5, 15), 282 windowBounds = Rect(0, 0, 1800, 2600) 283 ) 284 positioner.update(deviceConfig) 285 val minHeight = 286 context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height) 287 val bubble = 288 Bubble( 289 "key", 290 ShortcutInfo.Builder(context, "id").build(), 291 minHeight + 100 /* desiredHeight */, 292 0 /* desiredHeightResId */, 293 "title", 294 0 /* taskId */, 295 null /* locus */, 296 true /* isDismissable */, 297 directExecutor() 298 ) {} 299 300 // Ensure the height is the same as the desired value 301 assertThat(positioner.getExpandedViewHeight(bubble)) 302 .isEqualTo(bubble.getDesiredHeight(context)) 303 } 304 305 @Test testGetExpandedViewHeight_customHeight_tooSmallnull306 fun testGetExpandedViewHeight_customHeight_tooSmall() { 307 val deviceConfig = 308 defaultDeviceConfig.copy( 309 isLargeScreen = true, 310 insets = Insets.of(10, 20, 5, 15), 311 windowBounds = Rect(0, 0, 1800, 2600) 312 ) 313 positioner.update(deviceConfig) 314 315 val bubble = 316 Bubble( 317 "key", 318 ShortcutInfo.Builder(context, "id").build(), 319 10 /* desiredHeight */, 320 0 /* desiredHeightResId */, 321 "title", 322 0 /* taskId */, 323 null /* locus */, 324 true /* isDismissable */, 325 directExecutor() 326 ) {} 327 328 // Ensure the height is the same as the desired value 329 val minHeight = 330 context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height) 331 assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight) 332 } 333 334 @Test testGetMaxExpandedViewHeight_onLargeTabletnull335 fun testGetMaxExpandedViewHeight_onLargeTablet() { 336 val deviceConfig = 337 defaultDeviceConfig.copy( 338 isLargeScreen = true, 339 insets = Insets.of(10, 20, 5, 15), 340 windowBounds = Rect(0, 0, 1800, 2600) 341 ) 342 positioner.update(deviceConfig) 343 344 val manageButtonHeight = 345 context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) 346 val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width) 347 val expandedViewPadding = 348 context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) 349 val expectedHeight = 350 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2 351 assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */)) 352 .isEqualTo(expectedHeight) 353 } 354 355 @Test testAreBubblesBottomAligned_largeScreen_truenull356 fun testAreBubblesBottomAligned_largeScreen_true() { 357 val deviceConfig = 358 defaultDeviceConfig.copy( 359 isLargeScreen = true, 360 insets = Insets.of(10, 20, 5, 15), 361 windowBounds = Rect(0, 0, 1800, 2600) 362 ) 363 positioner.update(deviceConfig) 364 365 assertThat(positioner.areBubblesBottomAligned()).isTrue() 366 } 367 368 @Test testAreBubblesBottomAligned_largeScreen_landscape_falsenull369 fun testAreBubblesBottomAligned_largeScreen_landscape_false() { 370 val deviceConfig = 371 defaultDeviceConfig.copy( 372 isLargeScreen = true, 373 isLandscape = true, 374 insets = Insets.of(10, 20, 5, 15), 375 windowBounds = Rect(0, 0, 1800, 2600) 376 ) 377 positioner.update(deviceConfig) 378 379 assertThat(positioner.areBubblesBottomAligned()).isFalse() 380 } 381 382 @Test testAreBubblesBottomAligned_smallTablet_falsenull383 fun testAreBubblesBottomAligned_smallTablet_false() { 384 val deviceConfig = 385 defaultDeviceConfig.copy( 386 isLargeScreen = true, 387 isSmallTablet = true, 388 insets = Insets.of(10, 20, 5, 15), 389 windowBounds = Rect(0, 0, 1800, 2600) 390 ) 391 positioner.update(deviceConfig) 392 393 assertThat(positioner.areBubblesBottomAligned()).isFalse() 394 } 395 396 @Test testAreBubblesBottomAligned_phone_falsenull397 fun testAreBubblesBottomAligned_phone_false() { 398 val deviceConfig = 399 defaultDeviceConfig.copy( 400 insets = Insets.of(10, 20, 5, 15), 401 windowBounds = Rect(0, 0, 1800, 2600) 402 ) 403 positioner.update(deviceConfig) 404 405 assertThat(positioner.areBubblesBottomAligned()).isFalse() 406 } 407 408 @Test testExpandedViewY_phoneLandscapenull409 fun testExpandedViewY_phoneLandscape() { 410 val deviceConfig = 411 defaultDeviceConfig.copy( 412 isLandscape = true, 413 insets = Insets.of(10, 20, 5, 15), 414 windowBounds = Rect(0, 0, 1800, 2600) 415 ) 416 positioner.update(deviceConfig) 417 418 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 419 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 420 421 // This bubble will have max height so it'll always be top aligned 422 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 423 .isEqualTo(positioner.getExpandedViewYTopAligned()) 424 } 425 426 @Test testExpandedViewY_phonePortraitnull427 fun testExpandedViewY_phonePortrait() { 428 val deviceConfig = 429 defaultDeviceConfig.copy( 430 insets = Insets.of(10, 20, 5, 15), 431 windowBounds = Rect(0, 0, 1800, 2600) 432 ) 433 positioner.update(deviceConfig) 434 435 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 436 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 437 438 // Always top aligned in phone portrait 439 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 440 .isEqualTo(positioner.getExpandedViewYTopAligned()) 441 } 442 443 @Test testExpandedViewY_smallTabletLandscapenull444 fun testExpandedViewY_smallTabletLandscape() { 445 val deviceConfig = 446 defaultDeviceConfig.copy( 447 isSmallTablet = true, 448 isLandscape = true, 449 insets = Insets.of(10, 20, 5, 15), 450 windowBounds = Rect(0, 0, 1800, 2600) 451 ) 452 positioner.update(deviceConfig) 453 454 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 455 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 456 457 // This bubble will have max height which is always top aligned on small tablets 458 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 459 .isEqualTo(positioner.getExpandedViewYTopAligned()) 460 } 461 462 @Test testExpandedViewY_smallTabletPortraitnull463 fun testExpandedViewY_smallTabletPortrait() { 464 val deviceConfig = 465 defaultDeviceConfig.copy( 466 isSmallTablet = true, 467 insets = Insets.of(10, 20, 5, 15), 468 windowBounds = Rect(0, 0, 1800, 2600) 469 ) 470 positioner.update(deviceConfig) 471 472 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 473 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 474 475 // This bubble will have max height which is always top aligned on small tablets 476 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 477 .isEqualTo(positioner.getExpandedViewYTopAligned()) 478 } 479 480 @Test testExpandedViewY_largeScreenLandscapenull481 fun testExpandedViewY_largeScreenLandscape() { 482 val deviceConfig = 483 defaultDeviceConfig.copy( 484 isLargeScreen = true, 485 isLandscape = true, 486 insets = Insets.of(10, 20, 5, 15), 487 windowBounds = Rect(0, 0, 1800, 2600) 488 ) 489 positioner.update(deviceConfig) 490 491 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 492 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 493 494 // This bubble will have max height which is always top aligned on landscape, large tablet 495 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 496 .isEqualTo(positioner.getExpandedViewYTopAligned()) 497 } 498 499 @Test testExpandedViewY_largeScreenPortraitnull500 fun testExpandedViewY_largeScreenPortrait() { 501 val deviceConfig = 502 defaultDeviceConfig.copy( 503 isLargeScreen = true, 504 insets = Insets.of(10, 20, 5, 15), 505 windowBounds = Rect(0, 0, 1800, 2600) 506 ) 507 positioner.update(deviceConfig) 508 509 val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) 510 val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) 511 512 val manageButtonHeight = 513 context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) 514 val manageButtonPlusMargin = 515 manageButtonHeight + 516 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin) 517 val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width) 518 519 val expectedExpandedViewY = 520 positioner.availableRect.bottom - 521 manageButtonPlusMargin - 522 positioner.getExpandedViewHeightForLargeScreen() - 523 pointerWidth 524 525 // Bubbles are bottom aligned on portrait, large tablet 526 assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) 527 .isEqualTo(expectedExpandedViewY) 528 } 529 530 @Test testGetTaskViewContentWidth_onLeftnull531 fun testGetTaskViewContentWidth_onLeft() { 532 positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0))) 533 val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */) 534 val paddings = 535 positioner.getExpandedViewContainerPadding(true /* onLeft */, false /* isOverflow */) 536 assertThat(taskViewWidth) 537 .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2]) 538 } 539 540 @Test testGetTaskViewContentWidth_onRightnull541 fun testGetTaskViewContentWidth_onRight() { 542 positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0))) 543 val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */) 544 val paddings = 545 positioner.getExpandedViewContainerPadding(false /* onLeft */, false /* isOverflow */) 546 assertThat(taskViewWidth) 547 .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2]) 548 } 549 550 @Test testIsBubbleBarOnLeft_defaultsToRightnull551 fun testIsBubbleBarOnLeft_defaultsToRight() { 552 positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT 553 assertThat(positioner.isBubbleBarOnLeft).isFalse() 554 555 // Check that left and right return expected position 556 positioner.bubbleBarLocation = BubbleBarLocation.LEFT 557 assertThat(positioner.isBubbleBarOnLeft).isTrue() 558 positioner.bubbleBarLocation = BubbleBarLocation.RIGHT 559 assertThat(positioner.isBubbleBarOnLeft).isFalse() 560 } 561 562 @Test testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeftnull563 fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() { 564 positioner.update(defaultDeviceConfig.copy(isRtl = true)) 565 566 positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT 567 assertThat(positioner.isBubbleBarOnLeft).isTrue() 568 569 // Check that left and right return expected position 570 positioner.bubbleBarLocation = BubbleBarLocation.LEFT 571 assertThat(positioner.isBubbleBarOnLeft).isTrue() 572 positioner.bubbleBarLocation = BubbleBarLocation.RIGHT 573 assertThat(positioner.isBubbleBarOnLeft).isFalse() 574 } 575 576 @Test testGetBubbleBarExpandedViewBounds_onLeftnull577 fun testGetBubbleBarExpandedViewBounds_onLeft() { 578 testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false) 579 } 580 581 @Test testGetBubbleBarExpandedViewBounds_onRightnull582 fun testGetBubbleBarExpandedViewBounds_onRight() { 583 testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false) 584 } 585 586 @Test testGetBubbleBarExpandedViewBounds_isOverflow_onLeftnull587 fun testGetBubbleBarExpandedViewBounds_isOverflow_onLeft() { 588 testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true) 589 } 590 591 @Test testGetBubbleBarExpandedViewBounds_isOverflow_onRightnull592 fun testGetBubbleBarExpandedViewBounds_isOverflow_onRight() { 593 testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true) 594 } 595 testGetBubbleBarExpandedViewBoundsnull596 private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) { 597 positioner.setShowingInBubbleBar(true) 598 val windowBounds = Rect(0, 0, 2000, 2600) 599 val insets = Insets.of(10, 20, 5, 15) 600 val deviceConfig = 601 defaultDeviceConfig.copy( 602 isLargeScreen = true, 603 isLandscape = true, 604 insets = insets, 605 windowBounds = windowBounds 606 ) 607 positioner.update(deviceConfig) 608 609 val bubbleBarHeight = 100 610 positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight 611 612 val expandedViewPadding = 613 context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) 614 615 val left: Int 616 val right: Int 617 if (onLeft) { 618 // Pin to the left, calculate right 619 left = deviceConfig.insets.left + expandedViewPadding 620 right = left + positioner.getExpandedViewWidthForBubbleBar(isOverflow) 621 } else { 622 // Pin to the right, calculate left 623 right = 624 deviceConfig.windowBounds.right - deviceConfig.insets.right - expandedViewPadding 625 left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow) 626 } 627 // Above the bubble bar 628 val bottom = positioner.bubbleBarTopOnScreen - expandedViewPadding 629 // Calculate right and top based on size 630 val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow) 631 val expectedBounds = Rect(left, top, right, bottom) 632 633 val bounds = Rect() 634 positioner.getBubbleBarExpandedViewBounds(onLeft, isOverflow, bounds) 635 636 assertThat(bounds).isEqualTo(expectedBounds) 637 } 638 639 private val defaultYPosition: Float 640 /** 641 * Calculates the Y position bubbles should be placed based on the config. Based on the 642 * calculations in [BubblePositioner.getDefaultStartPosition] and 643 * [BubbleStackView.RelativeStackPosition]. 644 */ 645 get() { 646 val isTablet = positioner.isLargeScreen 647 648 // On tablet the position is centered, on phone it is an offset from the top. 649 val desiredY = 650 if (isTablet) { 651 positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f 652 } else { 653 context.resources 654 .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y) 655 .toFloat() 656 } 657 // Since we're visually centering the bubbles on tablet, use total screen height rather 658 // than the available height. 659 val height = 660 if (isTablet) { 661 positioner.screenRect.height() 662 } else { 663 positioner.availableRect.height() 664 } 665 val offsetPercent = (desiredY / height).coerceIn(0f, 1f) 666 val allowableStackRegion = 667 positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) 668 return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent 669 } 670 } 671