1 package com.android.systemui.statusbar.notification.stack 2 3 import android.testing.AndroidTestingRunner 4 import android.testing.TestableLooper.RunWithLooper 5 import android.view.LayoutInflater 6 import android.widget.FrameLayout 7 import androidx.test.filters.SmallTest 8 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress 9 import com.android.systemui.R 10 import com.android.systemui.SysuiTestCase 11 import com.android.systemui.animation.ShadeInterpolation 12 import com.android.systemui.flags.FakeFeatureFlags 13 import com.android.systemui.flags.FeatureFlags 14 import com.android.systemui.flags.Flags 15 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator 16 import com.android.systemui.statusbar.NotificationShelf 17 import com.android.systemui.statusbar.StatusBarIconView 18 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 19 import com.android.systemui.statusbar.notification.row.ExpandableView 20 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState 21 import com.android.systemui.util.mockito.mock 22 import junit.framework.Assert.assertEquals 23 import junit.framework.Assert.assertFalse 24 import junit.framework.Assert.assertTrue 25 import org.junit.Assume.assumeTrue 26 import org.junit.Before 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 import org.mockito.Mock 30 import org.mockito.Mockito.mock 31 import org.mockito.MockitoAnnotations 32 import org.mockito.Mockito.`when` as whenever 33 34 /** 35 * Tests for {@link NotificationShelf}. 36 */ 37 @SmallTest 38 @RunWith(AndroidTestingRunner::class) 39 @RunWithLooper 40 open class NotificationShelfTest : SysuiTestCase() { 41 42 open val useShelfRefactor: Boolean = false 43 open val useSensitiveReveal: Boolean = false 44 private val flags = FakeFeatureFlags() 45 46 @Mock 47 private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator 48 @Mock 49 private lateinit var ambientState: AmbientState 50 @Mock 51 private lateinit var hostLayoutController: NotificationStackScrollLayoutController 52 @Mock 53 private lateinit var hostLayout: NotificationStackScrollLayout 54 @Mock 55 private lateinit var roundnessManager: NotificationRoundnessManager 56 57 private lateinit var shelf: NotificationShelf 58 59 @Before 60 fun setUp() { 61 MockitoAnnotations.initMocks(this) 62 mDependency.injectTestDependency(FeatureFlags::class.java, flags) 63 flags.set(Flags.NOTIFICATION_SHELF_REFACTOR, useShelfRefactor) 64 flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal) 65 flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS) 66 val root = FrameLayout(context) 67 shelf = LayoutInflater.from(root.context) 68 .inflate(/* resource = */ R.layout.status_bar_notification_shelf, 69 /* root = */root, 70 /* attachToRoot = */false) as NotificationShelf 71 72 whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator) 73 whenever(ambientState.isSmallScreen).thenReturn(true) 74 75 if (useShelfRefactor) { 76 shelf.bind(ambientState, hostLayout, roundnessManager) 77 } else { 78 shelf.bind(ambientState, hostLayoutController) 79 } 80 shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5) 81 } 82 83 @Test 84 fun testShadeWidth_BasedOnFractionToShade() { 85 setFractionToShade(0f) 86 setOnLockscreen(true) 87 88 shelf.updateActualWidth(/* fractionToShade */ 0f, /* shortestWidth */ 10f) 89 assertTrue(shelf.actualWidth == 10) 90 91 shelf.updateActualWidth(/* fractionToShade */ 0.5f, /* shortestWidth */ 10f) 92 assertTrue(shelf.actualWidth == 20) 93 94 shelf.updateActualWidth(/* fractionToShade */ 1f, /* shortestWidth */ 10f) 95 assertTrue(shelf.actualWidth == 30) 96 } 97 98 @Test 99 fun testShelfIsLong_WhenNotOnLockscreen() { 100 setFractionToShade(0f) 101 setOnLockscreen(false) 102 103 shelf.updateActualWidth(/* fraction */ 0f, /* shortestWidth */ 10f) 104 assertTrue(shelf.actualWidth == 30) 105 } 106 107 @Test 108 fun testX_inViewForClick() { 109 val isXInView = shelf.isXInView( 110 /* localX */ 5f, 111 /* slop */ 5f, 112 /* left */ 0f, 113 /* right */ 10f) 114 assertTrue(isXInView) 115 } 116 117 @Test 118 fun testXSlop_inViewForClick() { 119 val isLeftXSlopInView = shelf.isXInView( 120 /* localX */ -3f, 121 /* slop */ 5f, 122 /* left */ 0f, 123 /* right */ 10f) 124 assertTrue(isLeftXSlopInView) 125 126 val isRightXSlopInView = shelf.isXInView( 127 /* localX */ 13f, 128 /* slop */ 5f, 129 /* left */ 0f, 130 /* right */ 10f) 131 assertTrue(isRightXSlopInView) 132 } 133 134 @Test 135 fun testX_notInViewForClick() { 136 val isXLeftOfShelfInView = shelf.isXInView( 137 /* localX */ -10f, 138 /* slop */ 5f, 139 /* left */ 0f, 140 /* right */ 10f) 141 assertFalse(isXLeftOfShelfInView) 142 143 val isXRightOfShelfInView = shelf.isXInView( 144 /* localX */ 20f, 145 /* slop */ 5f, 146 /* left */ 0f, 147 /* right */ 10f) 148 assertFalse(isXRightOfShelfInView) 149 } 150 151 @Test 152 fun testY_inViewForClick() { 153 val isYInView = shelf.isYInView( 154 /* localY */ 5f, 155 /* slop */ 5f, 156 /* top */ 0f, 157 /* bottom */ 10f) 158 assertTrue(isYInView) 159 } 160 161 @Test 162 fun testYSlop_inViewForClick() { 163 val isTopYSlopInView = shelf.isYInView( 164 /* localY */ -3f, 165 /* slop */ 5f, 166 /* top */ 0f, 167 /* bottom */ 10f) 168 assertTrue(isTopYSlopInView) 169 170 val isBottomYSlopInView = shelf.isYInView( 171 /* localY */ 13f, 172 /* slop */ 5f, 173 /* top */ 0f, 174 /* bottom */ 10f) 175 assertTrue(isBottomYSlopInView) 176 } 177 178 @Test 179 fun testY_notInViewForClick() { 180 val isYAboveShelfInView = shelf.isYInView( 181 /* localY */ -10f, 182 /* slop */ 5f, 183 /* top */ 0f, 184 /* bottom */ 5f) 185 assertFalse(isYAboveShelfInView) 186 187 val isYBelowShelfInView = shelf.isYInView( 188 /* localY */ 15f, 189 /* slop */ 5f, 190 /* top */ 0f, 191 /* bottom */ 5f) 192 assertFalse(isYBelowShelfInView) 193 } 194 195 @Test 196 fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() { 197 val shelfClipStart = 0f 198 val viewStart = 1f 199 200 val expandableView = mock(ExpandableView::class.java) 201 whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) 202 whenever(expandableView.translationY).thenReturn(viewStart) 203 whenever(expandableView.actualHeight).thenReturn(20) 204 205 whenever(expandableView.minHeight).thenReturn(20) 206 whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY 207 whenever(expandableView.isInShelf).thenReturn(true) 208 209 whenever(ambientState.isOnKeyguard).thenReturn(true) 210 whenever(ambientState.isExpansionChanging).thenReturn(false) 211 whenever(ambientState.isShadeExpanded).thenReturn(true) 212 213 val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, 214 /* view= */ expandableView, 215 /* scrollingFast= */ false, 216 /* expandingAnimated= */ false, 217 /* isLastChild= */ true, 218 shelfClipStart) 219 assertEquals(1f, amountInShelf) 220 } 221 222 @Test 223 fun getAmountInShelf_lastViewAlmostBelowShelf_completelyInShelf() { 224 val viewStart = 0f 225 val shelfClipStart = 0.001f 226 227 val expandableView = mock(ExpandableView::class.java) 228 whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) 229 whenever(expandableView.translationY).thenReturn(viewStart) 230 whenever(expandableView.actualHeight).thenReturn(20) 231 232 whenever(expandableView.minHeight).thenReturn(20) 233 whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY 234 whenever(expandableView.isInShelf).thenReturn(true) 235 236 whenever(ambientState.isOnKeyguard).thenReturn(true) 237 whenever(ambientState.isExpansionChanging).thenReturn(false) 238 whenever(ambientState.isShadeExpanded).thenReturn(true) 239 240 val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, 241 /* view= */ expandableView, 242 /* scrollingFast= */ false, 243 /* expandingAnimated= */ false, 244 /* isLastChild= */ true, 245 shelfClipStart) 246 assertEquals(1f, amountInShelf) 247 } 248 249 @Test 250 fun getAmountInShelf_lastViewHalfClippedByShelf_halfInShelf() { 251 val viewStart = 0f 252 val shelfClipStart = 10f 253 254 val expandableView = mock(ExpandableView::class.java) 255 whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) 256 whenever(expandableView.translationY).thenReturn(viewStart) 257 whenever(expandableView.actualHeight).thenReturn(25) 258 259 whenever(expandableView.minHeight).thenReturn(25) 260 whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY 261 whenever(expandableView.isInShelf).thenReturn(true) 262 263 whenever(ambientState.isOnKeyguard).thenReturn(true) 264 whenever(ambientState.isExpansionChanging).thenReturn(false) 265 whenever(ambientState.isShadeExpanded).thenReturn(true) 266 267 val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, 268 /* view= */ expandableView, 269 /* scrollingFast= */ false, 270 /* expandingAnimated= */ false, 271 /* isLastChild= */ true, 272 shelfClipStart) 273 assertEquals(0.5f, amountInShelf) 274 } 275 276 @Test 277 fun getAmountInShelf_lastViewAboveShelf_notInShelf() { 278 val viewStart = 0f 279 val shelfClipStart = 15f 280 281 val expandableView = mock(ExpandableView::class.java) 282 whenever(expandableView.shelfIcon).thenReturn(mock(StatusBarIconView::class.java)) 283 whenever(expandableView.translationY).thenReturn(viewStart) 284 whenever(expandableView.actualHeight).thenReturn(10) 285 286 whenever(expandableView.minHeight).thenReturn(10) 287 whenever(expandableView.shelfTransformationTarget).thenReturn(null) // use translationY 288 whenever(expandableView.isInShelf).thenReturn(false) 289 290 whenever(ambientState.isExpansionChanging).thenReturn(false) 291 whenever(ambientState.isOnKeyguard).thenReturn(true) 292 293 val amountInShelf = shelf.getAmountInShelf(/* i= */ 0, 294 /* view= */ expandableView, 295 /* scrollingFast= */ false, 296 /* expandingAnimated= */ false, 297 /* isLastChild= */ true, 298 shelfClipStart) 299 assertEquals(0f, amountInShelf) 300 } 301 302 @Test 303 fun updateState_expansionChanging_shelfTransparent() { 304 updateState_expansionChanging_shelfAlphaUpdated( 305 expansionFraction = 0.25f, 306 expectedAlpha = 0.0f 307 ) 308 } 309 310 @Test 311 fun updateState_expansionChangingWhileBouncerInTransit_shelfTransparent() { 312 whenever(ambientState.isBouncerInTransit).thenReturn(true) 313 314 updateState_expansionChanging_shelfAlphaUpdated( 315 expansionFraction = 0.85f, 316 expectedAlpha = 0.0f 317 ) 318 } 319 320 @Test 321 fun updateState_expansionChanging_shelfAlphaUpdated() { 322 updateState_expansionChanging_shelfAlphaUpdated( 323 expansionFraction = 0.6f, 324 expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f), 325 ) 326 } 327 328 @Test 329 fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() { 330 val expansionFraction = 0.6f 331 whenever(ambientState.isSmallScreen).thenReturn(false) 332 whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction)) 333 .thenReturn(0.123f) 334 335 updateState_expansionChanging_shelfAlphaUpdated( 336 expansionFraction = expansionFraction, 337 expectedAlpha = 0.123f 338 ) 339 } 340 341 @Test 342 fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() { 343 whenever(ambientState.isBouncerInTransit).thenReturn(true) 344 345 updateState_expansionChanging_shelfAlphaUpdated( 346 expansionFraction = 0.95f, 347 expectedAlpha = aboutToShowBouncerProgress(0.95f), 348 ) 349 } 350 351 @Test 352 fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() { 353 whenever(ambientState.isBouncerInTransit).thenReturn(true) 354 355 updateState_expansionChanging_shelfAlphaUpdated( 356 expansionFraction = 0.95f, 357 expectedAlpha = aboutToShowBouncerProgress(0.95f), 358 ) 359 } 360 361 @Test 362 fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { 363 // GIVEN 364 assumeTrue(useSensitiveReveal) 365 whenever(ambientState.stackY).thenReturn(100f) 366 whenever(ambientState.stackHeight).thenReturn(100f) 367 val paddingBetweenElements = 368 context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) 369 val endOfStack = 200f + paddingBetweenElements 370 whenever(ambientState.isShadeExpanded).thenReturn(true) 371 val lastVisibleBackgroundChild = mock<ExpandableView>() 372 val expandableViewState = ExpandableViewState() 373 whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) 374 val stackScrollAlgorithmState = StackScrollAlgorithmState() 375 stackScrollAlgorithmState.firstViewInShelf = mock() 376 377 whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null) 378 379 // WHEN 380 shelf.updateState(stackScrollAlgorithmState, ambientState) 381 382 // THEN 383 val shelfState = shelf.viewState as NotificationShelf.ShelfState 384 assertEquals(true, shelfState.hidden) 385 assertEquals(endOfStack, shelfState.yTranslation) 386 } 387 388 @Test 389 fun updateState_withNullFirstViewInShelf_hideShelf() { 390 // GIVEN 391 assumeTrue(useSensitiveReveal) 392 whenever(ambientState.stackY).thenReturn(100f) 393 whenever(ambientState.stackHeight).thenReturn(100f) 394 val paddingBetweenElements = 395 context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) 396 val endOfStack = 200f + paddingBetweenElements 397 whenever(ambientState.isShadeExpanded).thenReturn(true) 398 val lastVisibleBackgroundChild = mock<ExpandableView>() 399 val expandableViewState = ExpandableViewState() 400 whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) 401 whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) 402 val stackScrollAlgorithmState = StackScrollAlgorithmState() 403 404 stackScrollAlgorithmState.firstViewInShelf = null 405 406 // WHEN 407 shelf.updateState(stackScrollAlgorithmState, ambientState) 408 409 // THEN 410 val shelfState = shelf.viewState as NotificationShelf.ShelfState 411 assertEquals(true, shelfState.hidden) 412 assertEquals(endOfStack, shelfState.yTranslation) 413 } 414 415 @Test 416 fun updateState_withCollapsedShade_hideShelf() { 417 // GIVEN 418 assumeTrue(useSensitiveReveal) 419 whenever(ambientState.stackY).thenReturn(100f) 420 whenever(ambientState.stackHeight).thenReturn(100f) 421 val paddingBetweenElements = 422 context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) 423 val endOfStack = 200f + paddingBetweenElements 424 val lastVisibleBackgroundChild = mock<ExpandableView>() 425 val expandableViewState = ExpandableViewState() 426 whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) 427 whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) 428 val stackScrollAlgorithmState = StackScrollAlgorithmState() 429 stackScrollAlgorithmState.firstViewInShelf = mock() 430 431 whenever(ambientState.isShadeExpanded).thenReturn(false) 432 433 // WHEN 434 shelf.updateState(stackScrollAlgorithmState, ambientState) 435 436 // THEN 437 val shelfState = shelf.viewState as NotificationShelf.ShelfState 438 assertEquals(true, shelfState.hidden) 439 assertEquals(endOfStack, shelfState.yTranslation) 440 } 441 442 @Test 443 fun updateState_withHiddenSectionBeforeShelf_hideShelf() { 444 // GIVEN 445 assumeTrue(useSensitiveReveal) 446 whenever(ambientState.stackY).thenReturn(100f) 447 whenever(ambientState.stackHeight).thenReturn(100f) 448 val paddingBetweenElements = 449 context.resources.getDimensionPixelSize(R.dimen.notification_divider_height) 450 val endOfStack = 200f + paddingBetweenElements 451 whenever(ambientState.isShadeExpanded).thenReturn(true) 452 val lastVisibleBackgroundChild = mock<ExpandableView>() 453 val expandableViewState = ExpandableViewState() 454 whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState) 455 val stackScrollAlgorithmState = StackScrollAlgorithmState() 456 whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild) 457 458 val ssaVisibleChild = mock<ExpandableView>() 459 val ssaVisibleChildState = ExpandableViewState() 460 ssaVisibleChildState.hidden = true 461 whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState) 462 463 val ssaVisibleChild1 = mock<ExpandableView>() 464 val ssaVisibleChildState1 = ExpandableViewState() 465 ssaVisibleChildState1.hidden = true 466 whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1) 467 468 stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild) 469 stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1) 470 whenever(ambientState.isExpansionChanging).thenReturn(true) 471 stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1 472 473 // WHEN 474 shelf.updateState(stackScrollAlgorithmState, ambientState) 475 476 // THEN 477 val shelfState = shelf.viewState as NotificationShelf.ShelfState 478 assertEquals(true, shelfState.hidden) 479 assertEquals(endOfStack, shelfState.yTranslation) 480 } 481 482 private fun setFractionToShade(fraction: Float) { 483 whenever(ambientState.fractionToShade).thenReturn(fraction) 484 } 485 486 private fun setOnLockscreen(isOnLockscreen: Boolean) { 487 whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen) 488 } 489 490 private fun updateState_expansionChanging_shelfAlphaUpdated( 491 expansionFraction: Float, 492 expectedAlpha: Float 493 ) { 494 whenever(ambientState.lastVisibleBackgroundChild) 495 .thenReturn(ExpandableNotificationRow(mContext, null)) 496 whenever(ambientState.isExpansionChanging).thenReturn(true) 497 whenever(ambientState.expansionFraction).thenReturn(expansionFraction) 498 whenever(hostLayoutController.speedBumpIndex).thenReturn(0) 499 500 shelf.updateState(StackScrollAlgorithmState(), ambientState) 501 502 assertEquals(expectedAlpha, shelf.viewState.alpha) 503 } 504 } 505 506 @SmallTest 507 @RunWith(AndroidTestingRunner::class) 508 @RunWithLooper 509 class NotificationShelfWithRefactorTest : NotificationShelfTest() { 510 override val useShelfRefactor: Boolean = true 511 } 512 513 @SmallTest 514 @RunWith(AndroidTestingRunner::class) 515 @RunWithLooper 516 class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() { 517 override val useSensitiveReveal: Boolean = true 518 } 519 520 @SmallTest 521 @RunWith(AndroidTestingRunner::class) 522 @RunWithLooper 523 class NotificationShelfWithBothFlagsTest : NotificationShelfTest() { 524 override val useShelfRefactor: Boolean = true 525 override val useSensitiveReveal: Boolean = true 526 } 527