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