1 /* 2 * Copyright (C) 2021 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.systemui.temporarydisplay 18 19 import android.content.Context 20 import android.graphics.Rect 21 import android.os.PowerManager 22 import android.view.View 23 import android.view.ViewGroup 24 import android.view.WindowManager 25 import android.view.accessibility.AccessibilityManager 26 import androidx.test.filters.SmallTest 27 import com.android.internal.logging.InstanceId 28 import com.android.internal.logging.testing.UiEventLoggerFake 29 import com.android.systemui.res.R 30 import com.android.systemui.SysuiTestCase 31 import com.android.systemui.dagger.qualifiers.Main 32 import com.android.systemui.dump.DumpManager 33 import com.android.systemui.statusbar.policy.ConfigurationController 34 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener 35 import com.android.systemui.util.concurrency.DelayableExecutor 36 import com.android.systemui.util.concurrency.FakeExecutor 37 import com.android.systemui.util.mockito.any 38 import com.android.systemui.util.mockito.argumentCaptor 39 import com.android.systemui.util.mockito.capture 40 import com.android.systemui.util.time.FakeSystemClock 41 import com.android.systemui.util.time.SystemClock 42 import com.android.systemui.util.wakelock.WakeLock 43 import com.android.systemui.util.wakelock.WakeLockFake 44 import com.google.common.truth.Truth.assertThat 45 import org.junit.Before 46 import org.junit.Test 47 import org.mockito.Mock 48 import org.mockito.Mockito.never 49 import org.mockito.Mockito.reset 50 import org.mockito.Mockito.times 51 import org.mockito.Mockito.verify 52 import org.mockito.Mockito.`when` as whenever 53 import org.mockito.MockitoAnnotations 54 55 @SmallTest 56 class TemporaryViewDisplayControllerTest : SysuiTestCase() { 57 private lateinit var underTest: TestController 58 59 private lateinit var fakeClock: FakeSystemClock 60 private lateinit var fakeExecutor: FakeExecutor 61 62 private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder 63 private lateinit var fakeWakeLock: WakeLockFake 64 65 private lateinit var fakeUiEventLogger: UiEventLoggerFake 66 private lateinit var uiEventLogger: TemporaryViewUiEventLogger 67 68 @Mock 69 private lateinit var logger: TemporaryViewLogger<ViewInfo> 70 @Mock 71 private lateinit var accessibilityManager: AccessibilityManager 72 @Mock 73 private lateinit var configurationController: ConfigurationController 74 @Mock 75 private lateinit var dumpManager: DumpManager 76 @Mock 77 private lateinit var windowManager: WindowManager 78 @Mock 79 private lateinit var powerManager: PowerManager 80 81 @Before setUpnull82 fun setUp() { 83 MockitoAnnotations.initMocks(this) 84 85 whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) 86 .thenAnswer { it.arguments[0] } 87 88 fakeClock = FakeSystemClock() 89 fakeExecutor = FakeExecutor(fakeClock) 90 91 fakeWakeLock = WakeLockFake() 92 fakeWakeLockBuilder = WakeLockFake.Builder(context) 93 fakeWakeLockBuilder.setWakeLock(fakeWakeLock) 94 95 fakeUiEventLogger = UiEventLoggerFake() 96 uiEventLogger = TemporaryViewUiEventLogger(fakeUiEventLogger) 97 98 underTest = TestController( 99 context, 100 logger, 101 windowManager, 102 fakeExecutor, 103 accessibilityManager, 104 configurationController, 105 dumpManager, 106 powerManager, 107 fakeWakeLockBuilder, 108 fakeClock, 109 uiEventLogger, 110 ) 111 underTest.start() 112 } 113 114 @Test displayView_viewAddedWithCorrectTitlenull115 fun displayView_viewAddedWithCorrectTitle() { 116 underTest.displayView( 117 ViewInfo( 118 name = "name", 119 windowTitle = "Fake Window Title", 120 ) 121 ) 122 123 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 124 verify(windowManager).addView(any(), capture(windowParamsCaptor)) 125 assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title") 126 } 127 128 @Test displayView_loggednull129 fun displayView_logged() { 130 val info = ViewInfo( 131 name = "name", 132 windowTitle = "Fake Window Title", 133 ) 134 135 underTest.displayView(info) 136 137 verify(logger).logViewAddition(info) 138 assertThat(fakeUiEventLogger.eventId(0)) 139 .isEqualTo(TemporaryViewUiEvent.TEMPORARY_VIEW_ADDED.id) 140 } 141 142 @Test displayView_wakeLockAcquirednull143 fun displayView_wakeLockAcquired() { 144 underTest.displayView(getState()) 145 146 assertThat(fakeWakeLock.isHeld).isTrue() 147 } 148 149 @Test displayView_screenAlreadyOn_wakeLockAcquirednull150 fun displayView_screenAlreadyOn_wakeLockAcquired() { 151 whenever(powerManager.isScreenOn).thenReturn(true) 152 153 underTest.displayView(getState()) 154 155 assertThat(fakeWakeLock.isHeld).isTrue() 156 } 157 158 @Test displayView_wakeLockCanBeReleasedAfterTimeOutnull159 fun displayView_wakeLockCanBeReleasedAfterTimeOut() { 160 underTest.displayView(getState()) 161 assertThat(fakeWakeLock.isHeld).isTrue() 162 163 fakeClock.advanceTime(TIMEOUT_MS + 1) 164 165 assertThat(fakeWakeLock.isHeld).isFalse() 166 } 167 168 @Test displayView_removeView_wakeLockCanBeReleasednull169 fun displayView_removeView_wakeLockCanBeReleased() { 170 underTest.displayView(getState()) 171 assertThat(fakeWakeLock.isHeld).isTrue() 172 173 underTest.removeView(DEFAULT_ID, "test reason") 174 175 assertThat(fakeWakeLock.isHeld).isFalse() 176 } 177 178 @Test displayView_twice_viewNotAddedTwicenull179 fun displayView_twice_viewNotAddedTwice() { 180 underTest.displayView(getState()) 181 reset(windowManager) 182 183 underTest.displayView(getState()) 184 verify(windowManager, never()).addView(any(), any()) 185 } 186 187 @Test displayView_twiceWithDifferentIds_oldViewRemovedNewViewAddednull188 fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() { 189 val listener = registerListener() 190 191 underTest.displayView( 192 ViewInfo( 193 name = "name", 194 id = "First", 195 windowTitle = "First Fake Window Title", 196 ) 197 ) 198 199 underTest.displayView( 200 ViewInfo( 201 name = "name", 202 id = "Second", 203 windowTitle = "Second Fake Window Title", 204 ) 205 ) 206 207 val viewCaptor = argumentCaptor<View>() 208 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 209 210 verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor)) 211 212 assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title") 213 assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title") 214 verify(windowManager).removeView(viewCaptor.allValues[0]) 215 // Since the controller is still storing the older view in case it'll get re-displayed 216 // later, the listener shouldn't be notified 217 assertThat(listener.permanentlyRemovedIds).isEmpty() 218 } 219 220 @Test displayView_viewDoesNotDisappearsBeforeTimeoutnull221 fun displayView_viewDoesNotDisappearsBeforeTimeout() { 222 val listener = registerListener() 223 224 val state = getState() 225 underTest.displayView(state) 226 reset(windowManager) 227 228 fakeClock.advanceTime(TIMEOUT_MS - 1) 229 230 verify(windowManager, never()).removeView(any()) 231 assertThat(listener.permanentlyRemovedIds).isEmpty() 232 } 233 234 @Test displayView_viewDisappearsAfterTimeoutnull235 fun displayView_viewDisappearsAfterTimeout() { 236 val listener = registerListener() 237 238 val state = getState() 239 underTest.displayView(state) 240 reset(windowManager) 241 242 fakeClock.advanceTime(TIMEOUT_MS + 1) 243 244 verify(windowManager).removeView(any()) 245 assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 246 } 247 248 @Test displayView_calledAgainBeforeTimeout_timeoutResetnull249 fun displayView_calledAgainBeforeTimeout_timeoutReset() { 250 val listener = registerListener() 251 252 // First, display the view 253 val state = getState() 254 underTest.displayView(state) 255 256 // After some time, re-display the view 257 val waitTime = 1000L 258 fakeClock.advanceTime(waitTime) 259 underTest.displayView(getState()) 260 261 // Wait until the timeout for the first display would've happened 262 fakeClock.advanceTime(TIMEOUT_MS - waitTime + 1) 263 264 // Verify we didn't hide the view 265 verify(windowManager, never()).removeView(any()) 266 assertThat(listener.permanentlyRemovedIds).isEmpty() 267 } 268 269 @Test displayView_calledAgainBeforeTimeout_eventuallyTimesOutnull270 fun displayView_calledAgainBeforeTimeout_eventuallyTimesOut() { 271 val listener = registerListener() 272 273 // First, display the view 274 val state = getState() 275 underTest.displayView(state) 276 277 // After some time, re-display the view 278 fakeClock.advanceTime(1000L) 279 underTest.displayView(getState()) 280 281 // Ensure we still hide the view eventually 282 fakeClock.advanceTime(TIMEOUT_MS + 1) 283 284 verify(windowManager).removeView(any()) 285 assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 286 } 287 288 @Test displayScaleChange_viewReinflatedWithMostRecentStatenull289 fun displayScaleChange_viewReinflatedWithMostRecentState() { 290 underTest.displayView(getState(name = "First name")) 291 underTest.displayView(getState(name = "Second name")) 292 reset(windowManager) 293 294 getConfigurationListener().onDensityOrFontScaleChanged() 295 296 verify(windowManager).removeView(any()) 297 verify(windowManager).addView(any(), any()) 298 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name") 299 } 300 301 @Test multipleViewsWithDifferentIds_moreRecentReplacesOldernull302 fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() { 303 val listener = registerListener() 304 305 underTest.displayView( 306 ViewInfo( 307 name = "name", 308 windowTitle = "First Fake Window Title", 309 id = "id1" 310 ) 311 ) 312 313 underTest.displayView( 314 ViewInfo( 315 name = "name", 316 windowTitle = "Second Fake Window Title", 317 id = "id2" 318 ) 319 ) 320 321 val viewCaptor = argumentCaptor<View>() 322 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 323 324 verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor)) 325 326 assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title") 327 assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title") 328 verify(windowManager).removeView(viewCaptor.allValues[0]) 329 verify(configurationController, never()).removeCallback(any()) 330 331 // Since the controller is still storing the older view in case it'll get re-displayed 332 // later, the listener shouldn't be notified 333 assertThat(listener.permanentlyRemovedIds).isEmpty() 334 } 335 336 @Test multipleViewsWithDifferentIds_newViewRemoved_previousViewIsDisplayednull337 fun multipleViewsWithDifferentIds_newViewRemoved_previousViewIsDisplayed() { 338 val listener = registerListener() 339 340 underTest.displayView(ViewInfo("First name", id = "id1")) 341 342 verify(windowManager).addView(any(), any()) 343 reset(windowManager) 344 345 underTest.displayView(ViewInfo("Second name", id = "id2")) 346 347 verify(windowManager).removeView(any()) 348 verify(windowManager).addView(any(), any()) 349 reset(windowManager) 350 assertThat(listener.permanentlyRemovedIds).isEmpty() 351 352 // WHEN the current view is removed 353 underTest.removeView("id2", "test reason") 354 355 // THEN it's correctly removed 356 verify(windowManager).removeView(any()) 357 assertThat(listener.permanentlyRemovedIds).containsExactly("id2") 358 359 // And the previous view is correctly added 360 verify(windowManager).addView(any(), any()) 361 assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") 362 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name") 363 364 // WHEN the previous view times out 365 reset(windowManager) 366 fakeClock.advanceTime(TIMEOUT_MS + 1) 367 368 // THEN it is also removed 369 verify(windowManager).removeView(any()) 370 assertThat(underTest.activeViews.size).isEqualTo(0) 371 verify(configurationController).removeCallback(any()) 372 assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id2", "id1")) 373 } 374 375 @Test multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayednull376 fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() { 377 val listener = registerListener() 378 379 underTest.displayView(ViewInfo("First name", id = "id1")) 380 381 verify(windowManager).addView(any(), any()) 382 reset(windowManager) 383 384 underTest.displayView(ViewInfo("Second name", id = "id2")) 385 386 verify(windowManager).removeView(any()) 387 verify(windowManager).addView(any(), any()) 388 reset(windowManager) 389 390 // WHEN an old view is removed 391 underTest.removeView("id1", "test reason") 392 393 // THEN we don't update anything except the listener 394 assertThat(listener.permanentlyRemovedIds).containsExactly("id1") 395 verify(windowManager, never()).removeView(any()) 396 assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2") 397 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name") 398 verify(configurationController, never()).removeCallback(any()) 399 400 fakeClock.advanceTime(TIMEOUT_MS + 1) 401 402 verify(windowManager).removeView(any()) 403 assertThat(underTest.activeViews.size).isEqualTo(0) 404 verify(configurationController).removeCallback(any()) 405 assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id1", "id2")) 406 } 407 408 @Test multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayednull409 fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() { 410 val listener = registerListener() 411 412 underTest.displayView(ViewInfo("First name", id = "id1")) 413 underTest.displayView(ViewInfo("Second name", id = "id2")) 414 underTest.displayView(ViewInfo("Third name", id = "id3")) 415 416 verify(windowManager, times(3)).addView(any(), any()) 417 verify(windowManager, times(2)).removeView(any()) 418 419 reset(windowManager) 420 underTest.removeView("id3", "test reason") 421 422 verify(windowManager).removeView(any()) 423 assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3")) 424 assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2") 425 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name") 426 verify(configurationController, never()).removeCallback(any()) 427 428 reset(windowManager) 429 underTest.removeView("id2", "test reason") 430 431 verify(windowManager).removeView(any()) 432 assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2")) 433 assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") 434 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name") 435 verify(configurationController, never()).removeCallback(any()) 436 437 reset(windowManager) 438 fakeClock.advanceTime(TIMEOUT_MS + 1) 439 440 verify(windowManager).removeView(any()) 441 assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2", "id1")) 442 assertThat(underTest.activeViews.size).isEqualTo(0) 443 verify(configurationController).removeCallback(any()) 444 } 445 446 @Test multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentStatenull447 fun multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentState() { 448 underTest.displayView(ViewInfo("First name", id = "id1")) 449 underTest.displayView(ViewInfo("New name", id = "id1")) 450 451 verify(windowManager).addView(any(), any()) 452 reset(windowManager) 453 454 underTest.displayView(ViewInfo("Second name", id = "id2")) 455 456 verify(windowManager).removeView(any()) 457 verify(windowManager).addView(any(), any()) 458 reset(windowManager) 459 460 underTest.removeView("id2", "test reason") 461 462 verify(windowManager).removeView(any()) 463 verify(windowManager).addView(any(), any()) 464 assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1") 465 assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name") 466 assertThat(underTest.activeViews[0].info.name).isEqualTo("New name") 467 468 reset(windowManager) 469 fakeClock.advanceTime(TIMEOUT_MS + 1) 470 471 verify(windowManager).removeView(any()) 472 assertThat(underTest.activeViews.size).isEqualTo(0) 473 } 474 475 @Test multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayednull476 fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() { 477 val listener = registerListener() 478 479 underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000)) 480 fakeClock.advanceTime(1000) 481 underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000)) 482 fakeClock.advanceTime(1000) 483 underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000)) 484 485 reset(windowManager) 486 fakeClock.advanceTime(20000 + 1) 487 488 verify(windowManager).removeView(any()) 489 verify(windowManager, never()).addView(any(), any()) 490 assertThat(underTest.activeViews.size).isEqualTo(0) 491 verify(configurationController).removeCallback(any()) 492 assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2", "id3") 493 } 494 495 @Test multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayednull496 fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() { 497 val listener = registerListener() 498 499 underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000)) 500 fakeClock.advanceTime(1000) 501 underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500)) 502 503 reset(windowManager) 504 fakeClock.advanceTime(2500 + 1) 505 // At this point, 3501ms have passed, so id1 only has 499ms left which is not enough. 506 // So, it shouldn't be displayed. 507 508 verify(windowManager, never()).addView(any(), any()) 509 assertThat(underTest.activeViews.size).isEqualTo(0) 510 verify(configurationController).removeCallback(any()) 511 assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2") 512 } 513 514 @Test lowerThenHigherPriority_higherReplacesLowernull515 fun lowerThenHigherPriority_higherReplacesLower() { 516 val listener = registerListener() 517 518 underTest.displayView( 519 ViewInfo( 520 name = "normal", 521 windowTitle = "Normal Window Title", 522 id = "normal", 523 priority = ViewPriority.NORMAL, 524 ) 525 ) 526 527 val viewCaptor = argumentCaptor<View>() 528 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 529 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 530 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title") 531 reset(windowManager) 532 533 underTest.displayView( 534 ViewInfo( 535 name = "critical", 536 windowTitle = "Critical Window Title", 537 id = "critical", 538 priority = ViewPriority.CRITICAL, 539 ) 540 ) 541 542 verify(windowManager).removeView(viewCaptor.value) 543 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 544 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title") 545 verify(configurationController, never()).removeCallback(any()) 546 // Since the controller is still storing the older view in case it'll get re-displayed 547 // later, the listener shouldn't be notified 548 assertThat(listener.permanentlyRemovedIds).isEmpty() 549 } 550 551 @Test lowerThenHigherPriority_lowerPriorityRedisplayednull552 fun lowerThenHigherPriority_lowerPriorityRedisplayed() { 553 val listener = registerListener() 554 555 underTest.displayView( 556 ViewInfo( 557 name = "normal", 558 windowTitle = "Normal Window Title", 559 id = "normal", 560 priority = ViewPriority.NORMAL, 561 timeoutMs = 10000 562 ) 563 ) 564 565 underTest.displayView( 566 ViewInfo( 567 name = "critical", 568 windowTitle = "Critical Window Title", 569 id = "critical", 570 priority = ViewPriority.CRITICAL, 571 timeoutMs = 2000 572 ) 573 ) 574 575 val viewCaptor = argumentCaptor<View>() 576 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 577 verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor)) 578 assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("Normal Window Title") 579 assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Critical Window Title") 580 verify(windowManager).removeView(viewCaptor.allValues[0]) 581 582 reset(windowManager) 583 584 // WHEN the critical's timeout has expired 585 fakeClock.advanceTime(2000 + 1) 586 587 // THEN the normal view is re-displayed 588 verify(windowManager).removeView(viewCaptor.allValues[1]) 589 assertThat(listener.permanentlyRemovedIds).containsExactly("critical") 590 verify(windowManager).addView(any(), capture(windowParamsCaptor)) 591 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title") 592 verify(configurationController, never()).removeCallback(any()) 593 } 594 595 @Test lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOutnull596 fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() { 597 val listener = registerListener() 598 599 underTest.displayView( 600 ViewInfo( 601 name = "normal", 602 windowTitle = "Normal Window Title", 603 id = "normal", 604 priority = ViewPriority.NORMAL, 605 timeoutMs = 1000 606 ) 607 ) 608 609 underTest.displayView( 610 ViewInfo( 611 name = "critical", 612 windowTitle = "Critical Window Title", 613 id = "critical", 614 priority = ViewPriority.CRITICAL, 615 timeoutMs = 2000 616 ) 617 ) 618 reset(windowManager) 619 620 // WHEN the critical's timeout has expired 621 fakeClock.advanceTime(2000 + 1) 622 623 // THEN the normal view is not re-displayed since it already timed out 624 verify(windowManager).removeView(any()) 625 verify(windowManager, never()).addView(any(), any()) 626 assertThat(underTest.activeViews).isEmpty() 627 verify(configurationController).removeCallback(any()) 628 assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal") 629 } 630 631 @Test higherThenLowerPriority_higherStaysDisplayednull632 fun higherThenLowerPriority_higherStaysDisplayed() { 633 underTest.displayView( 634 ViewInfo( 635 name = "critical", 636 windowTitle = "Critical Window Title", 637 id = "critical", 638 priority = ViewPriority.CRITICAL, 639 ) 640 ) 641 642 val viewCaptor = argumentCaptor<View>() 643 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 644 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 645 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title") 646 reset(windowManager) 647 648 underTest.displayView( 649 ViewInfo( 650 name = "normal", 651 windowTitle = "Normal Window Title", 652 id = "normal", 653 priority = ViewPriority.NORMAL, 654 ) 655 ) 656 657 verify(windowManager, never()).removeView(viewCaptor.value) 658 verify(windowManager, never()).addView(any(), any()) 659 assertThat(underTest.activeViews.size).isEqualTo(2) 660 verify(configurationController, never()).removeCallback(any()) 661 } 662 663 @Test higherThenLowerPriority_lowerEventuallyDisplayednull664 fun higherThenLowerPriority_lowerEventuallyDisplayed() { 665 val listener = registerListener() 666 667 underTest.displayView( 668 ViewInfo( 669 name = "critical", 670 windowTitle = "Critical Window Title", 671 id = "critical", 672 priority = ViewPriority.CRITICAL, 673 timeoutMs = 3000, 674 ) 675 ) 676 677 val viewCaptor = argumentCaptor<View>() 678 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 679 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 680 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title") 681 reset(windowManager) 682 683 underTest.displayView( 684 ViewInfo( 685 name = "normal", 686 windowTitle = "Normal Window Title", 687 id = "normal", 688 priority = ViewPriority.NORMAL, 689 timeoutMs = 5000, 690 ) 691 ) 692 693 verify(windowManager, never()).removeView(viewCaptor.value) 694 verify(windowManager, never()).addView(any(), any()) 695 assertThat(underTest.activeViews.size).isEqualTo(2) 696 697 // WHEN the first critical view has timed out 698 fakeClock.advanceTime(3000 + 1) 699 700 // THEN the second normal view is displayed 701 verify(windowManager).removeView(viewCaptor.value) 702 assertThat(listener.permanentlyRemovedIds).containsExactly("critical") 703 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 704 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title") 705 assertThat(underTest.activeViews.size).isEqualTo(1) 706 verify(configurationController, never()).removeCallback(any()) 707 } 708 709 @Test higherThenLowerPriority_lowerNotDisplayedBecauseTimedOutnull710 fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() { 711 val listener = registerListener() 712 713 underTest.displayView( 714 ViewInfo( 715 name = "critical", 716 windowTitle = "Critical Window Title", 717 id = "critical", 718 priority = ViewPriority.CRITICAL, 719 timeoutMs = 3000, 720 ) 721 ) 722 723 val viewCaptor = argumentCaptor<View>() 724 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 725 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 726 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title") 727 reset(windowManager) 728 729 underTest.displayView( 730 ViewInfo( 731 name = "normal", 732 windowTitle = "Normal Window Title", 733 id = "normal", 734 priority = ViewPriority.NORMAL, 735 timeoutMs = 200, 736 ) 737 ) 738 739 verify(windowManager, never()).removeView(viewCaptor.value) 740 verify(windowManager, never()).addView(any(), any()) 741 assertThat(underTest.activeViews.size).isEqualTo(2) 742 reset(windowManager) 743 744 // WHEN the first critical view has timed out 745 fakeClock.advanceTime(3000 + 1) 746 747 // THEN the second normal view is not displayed because it's already timed out 748 verify(windowManager).removeView(viewCaptor.value) 749 verify(windowManager, never()).addView(any(), any()) 750 assertThat(underTest.activeViews).isEmpty() 751 verify(configurationController).removeCallback(any()) 752 assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal") 753 } 754 755 @Test criticalThenNewCritical_newCriticalDisplayednull756 fun criticalThenNewCritical_newCriticalDisplayed() { 757 val listener = registerListener() 758 759 underTest.displayView( 760 ViewInfo( 761 name = "critical 1", 762 windowTitle = "Critical Window Title 1", 763 id = "critical1", 764 priority = ViewPriority.CRITICAL, 765 ) 766 ) 767 768 val viewCaptor = argumentCaptor<View>() 769 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 770 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 771 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 1") 772 reset(windowManager) 773 774 underTest.displayView( 775 ViewInfo( 776 name = "critical 2", 777 windowTitle = "Critical Window Title 2", 778 id = "critical2", 779 priority = ViewPriority.CRITICAL, 780 ) 781 ) 782 783 verify(windowManager).removeView(viewCaptor.value) 784 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 785 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2") 786 assertThat(underTest.activeViews.size).isEqualTo(2) 787 verify(configurationController, never()).removeCallback(any()) 788 // Since the controller is still storing the older view in case it'll get re-displayed 789 // later, the listener shouldn't be notified 790 assertThat(listener.permanentlyRemovedIds).isEmpty() 791 } 792 793 @Test normalThenNewNormal_newNormalDisplayednull794 fun normalThenNewNormal_newNormalDisplayed() { 795 val listener = registerListener() 796 797 underTest.displayView( 798 ViewInfo( 799 name = "normal 1", 800 windowTitle = "Normal Window Title 1", 801 id = "normal1", 802 priority = ViewPriority.NORMAL, 803 ) 804 ) 805 806 val viewCaptor = argumentCaptor<View>() 807 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 808 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 809 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 1") 810 reset(windowManager) 811 812 underTest.displayView( 813 ViewInfo( 814 name = "normal 2", 815 windowTitle = "Normal Window Title 2", 816 id = "normal2", 817 priority = ViewPriority.NORMAL, 818 ) 819 ) 820 821 verify(windowManager).removeView(viewCaptor.value) 822 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 823 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2") 824 assertThat(underTest.activeViews.size).isEqualTo(2) 825 verify(configurationController, never()).removeCallback(any()) 826 // Since the controller is still storing the older view in case it'll get re-displayed 827 // later, the listener shouldn't be notified 828 assertThat(listener.permanentlyRemovedIds).isEmpty() 829 } 830 831 @Test lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdatednull832 fun lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdated() { 833 // First, display a lower priority view 834 underTest.displayView( 835 ViewInfo( 836 name = "normal", 837 windowTitle = "Normal Window Title", 838 id = "normal", 839 priority = ViewPriority.NORMAL, 840 // At the end of the test, we'll verify that this information isn't re-displayed. 841 // Use a super long timeout so that, when we verify it wasn't re-displayed, we know 842 // that it wasn't because the view just timed out. 843 timeoutMs = 100000, 844 ) 845 ) 846 847 val viewCaptor = argumentCaptor<View>() 848 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 849 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 850 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title") 851 reset(windowManager) 852 853 // Then, display a higher priority view 854 fakeClock.advanceTime(1000) 855 underTest.displayView( 856 ViewInfo( 857 name = "critical", 858 windowTitle = "Critical Window Title", 859 id = "critical", 860 priority = ViewPriority.CRITICAL, 861 timeoutMs = 3000, 862 ) 863 ) 864 865 verify(windowManager).removeView(viewCaptor.value) 866 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 867 assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title") 868 assertThat(underTest.activeViews.size).isEqualTo(2) 869 reset(windowManager) 870 871 // While the higher priority view is displayed, update the lower priority view with new 872 // information 873 fakeClock.advanceTime(1000) 874 val updatedViewInfo = ViewInfo( 875 name = "normal with update", 876 windowTitle = "Normal Window Title", 877 id = "normal", 878 priority = ViewPriority.NORMAL, 879 timeoutMs = 4000, 880 ) 881 underTest.displayView(updatedViewInfo) 882 883 verify(windowManager, never()).removeView(viewCaptor.value) 884 verify(windowManager, never()).addView(any(), any()) 885 assertThat(underTest.activeViews.size).isEqualTo(2) 886 reset(windowManager) 887 888 // WHEN the higher priority view times out 889 fakeClock.advanceTime(2001) 890 891 // THEN the higher priority view disappears and the lower priority view *with the updated 892 // information* gets displayed. 893 verify(windowManager).removeView(viewCaptor.value) 894 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 895 assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title") 896 assertThat(underTest.activeViews.size).isEqualTo(1) 897 assertThat(underTest.mostRecentViewInfo).isEqualTo(updatedViewInfo) 898 reset(windowManager) 899 900 // WHEN the updated view times out 901 fakeClock.advanceTime(2001) 902 903 // THEN the old information is never displayed 904 verify(windowManager).removeView(viewCaptor.value) 905 verify(windowManager, never()).addView(any(), any()) 906 assertThat(underTest.activeViews.size).isEqualTo(0) 907 } 908 909 @Test oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdatednull910 fun oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdated() { 911 // First, display id1 view 912 underTest.displayView( 913 ViewInfo( 914 name = "name 1", 915 windowTitle = "Name 1 Title", 916 id = "id1", 917 priority = ViewPriority.NORMAL, 918 // At the end of the test, we'll verify that this information isn't re-displayed. 919 // Use a super long timeout so that, when we verify it wasn't re-displayed, we know 920 // that it wasn't because the view just timed out. 921 timeoutMs = 100000, 922 ) 923 ) 924 925 val viewCaptor = argumentCaptor<View>() 926 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 927 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 928 assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title") 929 reset(windowManager) 930 931 // Then, display a new id2 view 932 fakeClock.advanceTime(1000) 933 underTest.displayView( 934 ViewInfo( 935 name = "name 2", 936 windowTitle = "Name 2 Title", 937 id = "id2", 938 priority = ViewPriority.NORMAL, 939 timeoutMs = 3000, 940 ) 941 ) 942 943 verify(windowManager).removeView(viewCaptor.value) 944 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 945 assertThat(windowParamsCaptor.value.title).isEqualTo("Name 2 Title") 946 assertThat(underTest.activeViews.size).isEqualTo(2) 947 reset(windowManager) 948 949 // While the id2 view is displayed, re-display the id1 view with new information 950 fakeClock.advanceTime(1000) 951 val updatedViewInfo = ViewInfo( 952 name = "name 1 with update", 953 windowTitle = "Name 1 Title", 954 id = "id1", 955 priority = ViewPriority.NORMAL, 956 timeoutMs = 3000, 957 ) 958 underTest.displayView(updatedViewInfo) 959 960 verify(windowManager).removeView(viewCaptor.value) 961 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 962 assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title") 963 assertThat(underTest.activeViews.size).isEqualTo(2) 964 reset(windowManager) 965 966 // WHEN the id1 view with new information times out 967 fakeClock.advanceTime(3001) 968 969 // THEN the id1 view disappears and the old id1 information is never displayed 970 verify(windowManager).removeView(viewCaptor.value) 971 verify(windowManager, never()).addView(any(), any()) 972 assertThat(underTest.activeViews.size).isEqualTo(0) 973 } 974 975 @Test oldViewUpdatedWhileNewViewDisplayed_usesNewTimeoutnull976 fun oldViewUpdatedWhileNewViewDisplayed_usesNewTimeout() { 977 // First, display id1 view 978 underTest.displayView( 979 ViewInfo( 980 name = "name 1", 981 windowTitle = "Name 1 Title", 982 id = "id1", 983 priority = ViewPriority.NORMAL, 984 timeoutMs = 5000, 985 ) 986 ) 987 988 // Then, display a new id2 view 989 fakeClock.advanceTime(1000) 990 underTest.displayView( 991 ViewInfo( 992 name = "name 2", 993 windowTitle = "Name 2 Title", 994 id = "id2", 995 priority = ViewPriority.NORMAL, 996 timeoutMs = 3000, 997 ) 998 ) 999 reset(windowManager) 1000 1001 // While the id2 view is displayed, re-display the id1 view with new information *and a 1002 // longer timeout* 1003 fakeClock.advanceTime(1000) 1004 val updatedViewInfo = ViewInfo( 1005 name = "name 1 with update", 1006 windowTitle = "Name 1 Title", 1007 id = "id1", 1008 priority = ViewPriority.NORMAL, 1009 timeoutMs = 30000, 1010 ) 1011 underTest.displayView(updatedViewInfo) 1012 1013 val viewCaptor = argumentCaptor<View>() 1014 val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>() 1015 verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor)) 1016 assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title") 1017 assertThat(underTest.activeViews.size).isEqualTo(2) 1018 reset(windowManager) 1019 1020 // WHEN id1's *old* timeout occurs 1021 fakeClock.advanceTime(3001) 1022 1023 // THEN id1 is still displayed because it was updated with a new timeout 1024 verify(windowManager, never()).removeView(viewCaptor.value) 1025 assertThat(underTest.activeViews.size).isEqualTo(1) 1026 } 1027 1028 @Test removeView_viewRemovedAndRemovalLoggedAndListenerNotifiednull1029 fun removeView_viewRemovedAndRemovalLoggedAndListenerNotified() { 1030 val listener = registerListener() 1031 1032 // First, add the view 1033 underTest.displayView(getState()) 1034 1035 // Then, remove it 1036 val reason = "test reason" 1037 underTest.removeView(DEFAULT_ID, reason) 1038 1039 verify(windowManager).removeView(any()) 1040 verify(logger).logViewRemoval(DEFAULT_ID, reason) 1041 verify(configurationController).removeCallback(any()) 1042 assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1043 assertThat(fakeUiEventLogger.logs.size).isEqualTo(1) 1044 assertThat(fakeUiEventLogger.eventId(0)) 1045 .isEqualTo(TemporaryViewUiEvent.TEMPORARY_VIEW_ADDED.id) 1046 } 1047 1048 @Test removeView_noAdd_viewNotRemovedAndListenerNotNotifiednull1049 fun removeView_noAdd_viewNotRemovedAndListenerNotNotified() { 1050 val listener = registerListener() 1051 1052 underTest.removeView("id", "reason") 1053 1054 verify(windowManager, never()).removeView(any()) 1055 assertThat(listener.permanentlyRemovedIds).isEmpty() 1056 } 1057 1058 @Test listenerRegistered_notifiedOnRemovalnull1059 fun listenerRegistered_notifiedOnRemoval() { 1060 val listener = registerListener() 1061 underTest.displayView(getState()) 1062 1063 underTest.removeView(DEFAULT_ID, "reason") 1064 1065 assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1066 } 1067 1068 @Test listenerRegistered_notifiedOnTimedOutEvenWhenNotDisplayednull1069 fun listenerRegistered_notifiedOnTimedOutEvenWhenNotDisplayed() { 1070 val listener = registerListener() 1071 underTest.displayView( 1072 ViewInfo( 1073 id = "id1", 1074 name = "name1", 1075 timeoutMs = 3000, 1076 ), 1077 ) 1078 1079 // Display a second view 1080 underTest.displayView( 1081 ViewInfo( 1082 id = "id2", 1083 name = "name2", 1084 timeoutMs = 2500, 1085 ), 1086 ) 1087 1088 // WHEN the second view times out 1089 fakeClock.advanceTime(2501) 1090 1091 // THEN the listener is notified of both IDs, since id2 timed out and id1 doesn't have 1092 // enough time left to be redisplayed 1093 assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2") 1094 } 1095 1096 @Test multipleListeners_allNotifiednull1097 fun multipleListeners_allNotified() { 1098 val listener1 = registerListener() 1099 val listener2 = registerListener() 1100 val listener3 = registerListener() 1101 1102 underTest.displayView(getState()) 1103 1104 underTest.removeView(DEFAULT_ID, "reason") 1105 1106 assertThat(listener1.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1107 assertThat(listener2.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1108 assertThat(listener3.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1109 } 1110 1111 @Test sameListenerRegisteredMultipleTimes_onlyNotifiedOncenull1112 fun sameListenerRegisteredMultipleTimes_onlyNotifiedOnce() { 1113 val listener = registerListener() 1114 underTest.registerListener(listener) 1115 underTest.registerListener(listener) 1116 1117 underTest.displayView(getState()) 1118 1119 underTest.removeView(DEFAULT_ID, "reason") 1120 1121 assertThat(listener.permanentlyRemovedIds).hasSize(1) 1122 assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID) 1123 } 1124 registerListenernull1125 private fun registerListener(): Listener { 1126 return Listener().also { 1127 underTest.registerListener(it) 1128 } 1129 } 1130 getStatenull1131 private fun getState(name: String = "name") = ViewInfo(name) 1132 1133 private fun getConfigurationListener(): ConfigurationListener { 1134 val callbackCaptor = argumentCaptor<ConfigurationListener>() 1135 verify(configurationController).addCallback(capture(callbackCaptor)) 1136 return callbackCaptor.value 1137 } 1138 1139 inner class TestController( 1140 context: Context, 1141 logger: TemporaryViewLogger<ViewInfo>, 1142 windowManager: WindowManager, 1143 @Main mainExecutor: DelayableExecutor, 1144 accessibilityManager: AccessibilityManager, 1145 configurationController: ConfigurationController, 1146 dumpManager: DumpManager, 1147 powerManager: PowerManager, 1148 wakeLockBuilder: WakeLock.Builder, 1149 systemClock: SystemClock, 1150 uiEventLogger: TemporaryViewUiEventLogger, 1151 ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>( 1152 context, 1153 logger, 1154 windowManager, 1155 mainExecutor, 1156 accessibilityManager, 1157 configurationController, 1158 dumpManager, 1159 powerManager, 1160 R.layout.chipbar, 1161 wakeLockBuilder, 1162 systemClock, 1163 uiEventLogger, 1164 ) { 1165 var mostRecentViewInfo: ViewInfo? = null 1166 1167 override val windowLayoutParams = commonWindowLayoutParams 1168 updateViewnull1169 override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) { 1170 mostRecentViewInfo = newInfo 1171 } 1172 getTouchableRegionnull1173 override fun getTouchableRegion(view: View, outRect: Rect) { 1174 outRect.setEmpty() 1175 } 1176 startnull1177 override fun start() {} 1178 } 1179 1180 data class ViewInfo( 1181 val name: String, 1182 override val windowTitle: String = "Window Title", 1183 override val wakeReason: String = "WAKE_REASON", 1184 override val timeoutMs: Int = TIMEOUT_MS.toInt(), 1185 override val id: String = DEFAULT_ID, 1186 override val priority: ViewPriority = ViewPriority.NORMAL, 1187 override val instanceId: InstanceId = InstanceId.fakeInstanceId(0), 1188 ) : TemporaryViewInfo() 1189 1190 inner class Listener : TemporaryViewDisplayController.Listener { 1191 val permanentlyRemovedIds = mutableListOf<String>() onInfoPermanentlyRemovednull1192 override fun onInfoPermanentlyRemoved(id: String, reason: String) { 1193 permanentlyRemovedIds.add(id) 1194 } 1195 } 1196 } 1197 1198 private const val TIMEOUT_MS = 10000L 1199 private const val DEFAULT_ID = "defaultId" 1200