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