• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.statusbar.data.repository
18 
19 import android.graphics.Rect
20 import android.view.WindowInsets
21 import android.view.WindowInsetsController
22 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
23 import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
24 import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
25 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
26 import androidx.test.filters.SmallTest
27 import com.android.internal.statusbar.LetterboxDetails
28 import com.android.internal.view.AppearanceRegion
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.coroutines.collectLastValue
31 import com.android.systemui.statusbar.CommandQueue
32 import com.android.systemui.statusbar.data.model.StatusBarMode
33 import com.android.systemui.statusbar.phone.BoundsPair
34 import com.android.systemui.statusbar.phone.LetterboxAppearance
35 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
36 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
37 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
38 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
39 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
40 import com.android.systemui.util.mockito.any
41 import com.android.systemui.util.mockito.argumentCaptor
42 import com.android.systemui.util.mockito.capture
43 import com.android.systemui.util.mockito.eq
44 import com.android.systemui.util.mockito.mock
45 import com.android.systemui.util.mockito.whenever
46 import com.google.common.truth.Truth.assertThat
47 import kotlinx.coroutines.test.TestScope
48 import kotlinx.coroutines.test.runTest
49 import org.junit.Before
50 import org.junit.Test
51 import org.mockito.Mockito.verify
52 
53 @SmallTest
54 class StatusBarModeRepositoryImplTest : SysuiTestCase() {
55     private val testScope = TestScope()
56     private val commandQueue = mock<CommandQueue>()
57     private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
58     private val statusBarBoundsProvider = mock<StatusBarBoundsProvider>()
59     private val statusBarFragmentComponent =
<lambda>null60         mock<StatusBarFragmentComponent>().also {
61             whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
62         }
63     private val ongoingCallRepository = OngoingCallRepository()
64 
65     private val underTest =
66         StatusBarModePerDisplayRepositoryImpl(
67                 testScope.backgroundScope,
68                 DISPLAY_ID,
69                 commandQueue,
70                 letterboxAppearanceCalculator,
71                 ongoingCallRepository,
72             )
<lambda>null73             .apply {
74                 this.start()
75                 this.onStatusBarViewInitialized(statusBarFragmentComponent)
76             }
77 
78     private val commandQueueCallback: CommandQueue.Callbacks
79         get() {
80             val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
81             verify(commandQueue).addCallback(callbackCaptor.capture())
82             return callbackCaptor.value
83         }
84 
85     private val statusBarBoundsChangeListener: StatusBarBoundsProvider.BoundsChangeListener
86         get() {
87             val callbackCaptor = argumentCaptor<StatusBarBoundsProvider.BoundsChangeListener>()
88             verify(statusBarBoundsProvider).addChangeListener(capture(callbackCaptor))
89             return callbackCaptor.value
90         }
91 
setUpnull92     @Before fun setUp() {}
93 
94     @Test
isTransientShown_commandQueueShow_wrongDisplayId_notUpdatednull95     fun isTransientShown_commandQueueShow_wrongDisplayId_notUpdated() {
96         commandQueueCallback.showTransient(
97             DISPLAY_ID + 1,
98             WindowInsets.Type.statusBars(),
99             /* isGestureOnSystemBar= */ false,
100         )
101 
102         assertThat(underTest.isTransientShown.value).isFalse()
103     }
104 
105     @Test
isTransientShown_commandQueueShow_notStatusBarType_notUpdatednull106     fun isTransientShown_commandQueueShow_notStatusBarType_notUpdated() {
107         commandQueueCallback.showTransient(
108             DISPLAY_ID,
109             WindowInsets.Type.navigationBars(),
110             /* isGestureOnSystemBar= */ false,
111         )
112 
113         assertThat(underTest.isTransientShown.value).isFalse()
114     }
115 
116     @Test
isTransientShown_commandQueueShow_truenull117     fun isTransientShown_commandQueueShow_true() {
118         commandQueueCallback.showTransient(
119             DISPLAY_ID,
120             WindowInsets.Type.statusBars(),
121             /* isGestureOnSystemBar= */ false,
122         )
123 
124         assertThat(underTest.isTransientShown.value).isTrue()
125     }
126 
127     @Test
isTransientShown_commandQueueShow_statusBarAndOtherTypes_truenull128     fun isTransientShown_commandQueueShow_statusBarAndOtherTypes_true() {
129         commandQueueCallback.showTransient(
130             DISPLAY_ID,
131             WindowInsets.Type.statusBars().or(WindowInsets.Type.navigationBars()),
132             /* isGestureOnSystemBar= */ false,
133         )
134 
135         assertThat(underTest.isTransientShown.value).isTrue()
136     }
137 
138     @Test
isTransientShown_commandQueueAbort_wrongDisplayId_notUpdatednull139     fun isTransientShown_commandQueueAbort_wrongDisplayId_notUpdated() {
140         // Start as true
141         commandQueueCallback.showTransient(
142             DISPLAY_ID,
143             WindowInsets.Type.statusBars(),
144             /* isGestureOnSystemBar= */ false,
145         )
146         assertThat(underTest.isTransientShown.value).isTrue()
147 
148         // GIVEN the wrong display ID
149         commandQueueCallback.abortTransient(DISPLAY_ID + 1, WindowInsets.Type.statusBars())
150 
151         // THEN the old value remains
152         assertThat(underTest.isTransientShown.value).isTrue()
153     }
154 
155     @Test
isTransientShown_commandQueueAbort_notStatusBarType_notUpdatednull156     fun isTransientShown_commandQueueAbort_notStatusBarType_notUpdated() {
157         // Start as true
158         commandQueueCallback.showTransient(
159             DISPLAY_ID,
160             WindowInsets.Type.statusBars(),
161             /* isGestureOnSystemBar= */ false,
162         )
163         assertThat(underTest.isTransientShown.value).isTrue()
164 
165         // GIVEN the wrong type
166         commandQueueCallback.abortTransient(DISPLAY_ID, WindowInsets.Type.navigationBars())
167 
168         // THEN the old value remains
169         assertThat(underTest.isTransientShown.value).isTrue()
170     }
171 
172     @Test
isTransientShown_commandQueueAbort_falsenull173     fun isTransientShown_commandQueueAbort_false() {
174         // Start as true
175         commandQueueCallback.showTransient(
176             DISPLAY_ID,
177             WindowInsets.Type.statusBars(),
178             /* isGestureOnSystemBar= */ false,
179         )
180         assertThat(underTest.isTransientShown.value).isTrue()
181 
182         commandQueueCallback.abortTransient(DISPLAY_ID, WindowInsets.Type.statusBars())
183 
184         assertThat(underTest.isTransientShown.value).isFalse()
185     }
186 
187     @Test
isTransientShown_commandQueueAbort_statusBarAndOtherTypes_falsenull188     fun isTransientShown_commandQueueAbort_statusBarAndOtherTypes_false() {
189         // Start as true
190         commandQueueCallback.showTransient(
191             DISPLAY_ID,
192             WindowInsets.Type.statusBars(),
193             /* isGestureOnSystemBar= */ false,
194         )
195         assertThat(underTest.isTransientShown.value).isTrue()
196 
197         commandQueueCallback.abortTransient(
198             DISPLAY_ID,
199             WindowInsets.Type.statusBars().or(WindowInsets.Type.captionBar()),
200         )
201 
202         assertThat(underTest.isTransientShown.value).isFalse()
203     }
204 
205     @Test
isTransientShown_showTransient_truenull206     fun isTransientShown_showTransient_true() {
207         underTest.showTransient()
208 
209         assertThat(underTest.isTransientShown.value).isTrue()
210     }
211 
212     @Test
isTransientShown_clearTransient_falsenull213     fun isTransientShown_clearTransient_false() {
214         // Start as true
215         commandQueueCallback.showTransient(
216             DISPLAY_ID,
217             WindowInsets.Type.statusBars(),
218             /* isGestureOnSystemBar= */ false,
219         )
220         assertThat(underTest.isTransientShown.value).isTrue()
221 
222         underTest.clearTransient()
223 
224         assertThat(underTest.isTransientShown.value).isFalse()
225     }
226 
227     @Test
isInFullscreenMode_visibleTypesHasStatusBar_falsenull228     fun isInFullscreenMode_visibleTypesHasStatusBar_false() =
229         testScope.runTest {
230             val latest by collectLastValue(underTest.isInFullscreenMode)
231 
232             onSystemBarAttributesChanged(
233                 requestedVisibleTypes = WindowInsets.Type.statusBars(),
234             )
235 
236             assertThat(latest).isFalse()
237         }
238 
239     @Test
isInFullscreenMode_visibleTypesDoesNotHaveStatusBar_truenull240     fun isInFullscreenMode_visibleTypesDoesNotHaveStatusBar_true() =
241         testScope.runTest {
242             val latest by collectLastValue(underTest.isInFullscreenMode)
243 
244             onSystemBarAttributesChanged(
245                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
246             )
247 
248             assertThat(latest).isTrue()
249         }
250 
251     @Test
isInFullscreenMode_wrongDisplayId_notUpdatednull252     fun isInFullscreenMode_wrongDisplayId_notUpdated() =
253         testScope.runTest {
254             val latest by collectLastValue(underTest.isInFullscreenMode)
255 
256             onSystemBarAttributesChanged(
257                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
258             )
259             assertThat(latest).isTrue()
260 
261             onSystemBarAttributesChanged(
262                 displayId = DISPLAY_ID + 1,
263                 requestedVisibleTypes = WindowInsets.Type.statusBars(),
264             )
265 
266             assertThat(latest).isTrue()
267         }
268 
269     @Test
statusBarAppearance_navBarColorManaged_matchesCallbackValuenull270     fun statusBarAppearance_navBarColorManaged_matchesCallbackValue() =
271         testScope.runTest {
272             val latest by collectLastValue(underTest.statusBarAppearance)
273 
274             onSystemBarAttributesChanged(navbarColorManagedByIme = true)
275 
276             assertThat(latest!!.navbarColorManagedByIme).isTrue()
277 
278             onSystemBarAttributesChanged(navbarColorManagedByIme = false)
279 
280             assertThat(latest!!.navbarColorManagedByIme).isFalse()
281         }
282 
283     @Test
statusBarAppearance_appearanceRegions_noLetterboxDetails_usesCallbackValuesnull284     fun statusBarAppearance_appearanceRegions_noLetterboxDetails_usesCallbackValues() =
285         testScope.runTest {
286             val latest by collectLastValue(underTest.statusBarAppearance)
287 
288             whenever(
289                     letterboxAppearanceCalculator.getLetterboxAppearance(
290                         eq(APPEARANCE),
291                         eq(APPEARANCE_REGIONS),
292                         eq(LETTERBOX_DETAILS),
293                         any(),
294                     )
295                 )
296                 .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
297 
298             // WHEN the letterbox details are empty
299             onSystemBarAttributesChanged(
300                 appearance = APPEARANCE,
301                 appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
302                 letterboxDetails = emptyArray(),
303             )
304 
305             // THEN the appearance regions passed to the callback are used, *not*
306             // REGIONS_FROM_LETTERBOX_CALCULATOR
307             assertThat(latest!!.appearanceRegions).isEqualTo(APPEARANCE_REGIONS)
308             assertThat(latest!!.appearanceRegions).isNotEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
309         }
310 
311     @Test
statusBarAppearance_appearanceRegions_letterboxDetails_usesLetterboxCalculatornull312     fun statusBarAppearance_appearanceRegions_letterboxDetails_usesLetterboxCalculator() =
313         testScope.runTest {
314             val latest by collectLastValue(underTest.statusBarAppearance)
315 
316             whenever(
317                     letterboxAppearanceCalculator.getLetterboxAppearance(
318                         eq(APPEARANCE),
319                         eq(APPEARANCE_REGIONS),
320                         eq(LETTERBOX_DETAILS),
321                         any(),
322                     )
323                 )
324                 .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE)
325 
326             onSystemBarAttributesChanged(
327                 appearance = APPEARANCE,
328                 appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
329                 letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
330             )
331 
332             assertThat(latest!!.appearanceRegions).isEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR)
333         }
334 
335     @Test
statusBarAppearance_boundsChanged_appearanceReFetchednull336     fun statusBarAppearance_boundsChanged_appearanceReFetched() =
337         testScope.runTest {
338             val latest by collectLastValue(underTest.statusBarAppearance)
339 
340             // First, start with some appearances
341             val startingLetterboxAppearance =
342                 LetterboxAppearance(
343                     APPEARANCE_LIGHT_STATUS_BARS,
344                     listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1)))
345                 )
346             whenever(
347                     letterboxAppearanceCalculator.getLetterboxAppearance(
348                         eq(APPEARANCE),
349                         eq(APPEARANCE_REGIONS),
350                         eq(LETTERBOX_DETAILS),
351                         any(),
352                     )
353                 )
354                 .thenReturn(startingLetterboxAppearance)
355             onSystemBarAttributesChanged(
356                 appearance = APPEARANCE,
357                 appearanceRegions = APPEARANCE_REGIONS.toTypedArray(),
358                 letterboxDetails = LETTERBOX_DETAILS.toTypedArray(),
359             )
360             assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
361             assertThat(latest!!.appearanceRegions)
362                 .isEqualTo(startingLetterboxAppearance.appearanceRegions)
363 
364             // WHEN there's a new appearance and we get new status bar bounds
365             val newLetterboxAppearance =
366                 LetterboxAppearance(
367                     APPEARANCE_LOW_PROFILE_BARS,
368                     listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40)))
369                 )
370             whenever(
371                     letterboxAppearanceCalculator.getLetterboxAppearance(
372                         eq(APPEARANCE),
373                         eq(APPEARANCE_REGIONS),
374                         eq(LETTERBOX_DETAILS),
375                         any(),
376                     )
377                 )
378                 .thenReturn(newLetterboxAppearance)
379             statusBarBoundsChangeListener.onStatusBarBoundsChanged(
380                 BoundsPair(Rect(0, 0, 50, 50), Rect(0, 0, 60, 60))
381             )
382 
383             // THEN the new appearances are used
384             assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
385             assertThat(latest!!.appearanceRegions)
386                 .isEqualTo(newLetterboxAppearance.appearanceRegions)
387         }
388 
389     @Test
statusBarMode_ongoingCallAndFullscreen_semiTransparentnull390     fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
391         testScope.runTest {
392             val latest by collectLastValue(underTest.statusBarAppearance)
393 
394             ongoingCallRepository.setOngoingCallState(
395                 OngoingCallModel.InCall(startTimeMs = 34, intent = null)
396             )
397             onSystemBarAttributesChanged(
398                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
399             )
400 
401             assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
402         }
403 
404     @Test
statusBarMode_ongoingCallButNotFullscreen_matchesAppearancenull405     fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
406         testScope.runTest {
407             val latest by collectLastValue(underTest.statusBarAppearance)
408 
409             ongoingCallRepository.setOngoingCallState(
410                 OngoingCallModel.InCall(startTimeMs = 789, intent = null)
411             )
412             onSystemBarAttributesChanged(
413                 requestedVisibleTypes = WindowInsets.Type.statusBars(),
414                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
415             )
416 
417             assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
418         }
419 
420     @Test
statusBarMode_fullscreenButNotOngoingCall_matchesAppearancenull421     fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
422         testScope.runTest {
423             val latest by collectLastValue(underTest.statusBarAppearance)
424 
425             ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall)
426             onSystemBarAttributesChanged(
427                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
428                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
429             )
430 
431             assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
432         }
433 
434     @Test
statusBarMode_transientShown_semiTransparentnull435     fun statusBarMode_transientShown_semiTransparent() =
436         testScope.runTest {
437             val latest by collectLastValue(underTest.statusBarAppearance)
438             onSystemBarAttributesChanged(
439                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
440             )
441 
442             underTest.showTransient()
443 
444             assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
445         }
446 
447     @Test
statusBarMode_appearanceLowProfileAndOpaque_lightsOutnull448     fun statusBarMode_appearanceLowProfileAndOpaque_lightsOut() =
449         testScope.runTest {
450             val latest by collectLastValue(underTest.statusBarAppearance)
451 
452             onSystemBarAttributesChanged(
453                 appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS,
454             )
455 
456             assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT)
457         }
458 
459     @Test
statusBarMode_appearanceLowProfile_lightsOutTransparentnull460     fun statusBarMode_appearanceLowProfile_lightsOutTransparent() =
461         testScope.runTest {
462             val latest by collectLastValue(underTest.statusBarAppearance)
463 
464             onSystemBarAttributesChanged(
465                 appearance = APPEARANCE_LOW_PROFILE_BARS,
466             )
467 
468             assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
469         }
470 
471     @Test
statusBarMode_appearanceOpaque_opaquenull472     fun statusBarMode_appearanceOpaque_opaque() =
473         testScope.runTest {
474             val latest by collectLastValue(underTest.statusBarAppearance)
475 
476             onSystemBarAttributesChanged(
477                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
478             )
479 
480             assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
481         }
482 
483     @Test
statusBarMode_appearanceSemiTransparent_semiTransparentnull484     fun statusBarMode_appearanceSemiTransparent_semiTransparent() =
485         testScope.runTest {
486             val latest by collectLastValue(underTest.statusBarAppearance)
487 
488             onSystemBarAttributesChanged(
489                 appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
490             )
491 
492             assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
493         }
494 
495     @Test
statusBarMode_appearanceNone_transparentnull496     fun statusBarMode_appearanceNone_transparent() =
497         testScope.runTest {
498             val latest by collectLastValue(underTest.statusBarAppearance)
499 
500             onSystemBarAttributesChanged(
501                 appearance = 0,
502             )
503 
504             assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
505         }
506 
onSystemBarAttributesChangednull507     private fun onSystemBarAttributesChanged(
508         displayId: Int = DISPLAY_ID,
509         @WindowInsetsController.Appearance appearance: Int = APPEARANCE_OPAQUE_STATUS_BARS,
510         appearanceRegions: Array<AppearanceRegion> = emptyArray(),
511         navbarColorManagedByIme: Boolean = false,
512         @WindowInsetsController.Behavior behavior: Int = WindowInsetsController.BEHAVIOR_DEFAULT,
513         @WindowInsets.Type.InsetsType
514         requestedVisibleTypes: Int = WindowInsets.Type.defaultVisible(),
515         packageName: String = "package name",
516         letterboxDetails: Array<LetterboxDetails> = emptyArray(),
517     ) {
518         commandQueueCallback.onSystemBarAttributesChanged(
519             displayId,
520             appearance,
521             appearanceRegions,
522             navbarColorManagedByIme,
523             behavior,
524             requestedVisibleTypes,
525             packageName,
526             letterboxDetails,
527         )
528     }
529 
530     private companion object {
531         const val DISPLAY_ID = 5
532         private const val APPEARANCE = APPEARANCE_LIGHT_STATUS_BARS
533         private val APPEARANCE_REGION = AppearanceRegion(APPEARANCE, Rect(0, 0, 150, 300))
534         private val APPEARANCE_REGIONS = listOf(APPEARANCE_REGION)
535         private val LETTERBOX_DETAILS =
536             listOf(
537                 LetterboxDetails(
538                     /* letterboxInnerBounds= */ Rect(0, 0, 10, 10),
539                     /* letterboxFullBounds= */ Rect(0, 0, 20, 20),
540                     /* appAppearance= */ 0
541                 )
542             )
543         private val REGIONS_FROM_LETTERBOX_CALCULATOR =
544             listOf(AppearanceRegion(APPEARANCE, Rect(0, 0, 10, 20)))
545         private const val LETTERBOXED_APPEARANCE = APPEARANCE_LOW_PROFILE_BARS
546         private val CALCULATOR_LETTERBOX_APPEARANCE =
547             LetterboxAppearance(LETTERBOXED_APPEARANCE, REGIONS_FROM_LETTERBOX_CALCULATOR)
548     }
549 }
550