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