1 /*
<lambda>null2  * 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 
18 @file:OptIn(ExperimentalCoroutinesApi::class)
19 
20 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
21 
22 import android.platform.test.annotations.DisableFlags
23 import android.platform.test.annotations.EnableFlags
24 import android.platform.test.flag.junit.FlagsParameterization
25 import androidx.test.filters.SmallTest
26 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
27 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
28 import com.android.systemui.SysuiTestCase
29 import com.android.systemui.common.shared.model.NotificationContainerBounds
30 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
31 import com.android.systemui.coroutines.collectLastValue
32 import com.android.systemui.coroutines.collectValues
33 import com.android.systemui.flags.BrokenWithSceneContainer
34 import com.android.systemui.flags.DisableSceneContainer
35 import com.android.systemui.flags.EnableSceneContainer
36 import com.android.systemui.flags.Flags
37 import com.android.systemui.flags.andSceneContainer
38 import com.android.systemui.flags.fakeFeatureFlagsClassic
39 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
40 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
41 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
42 import com.android.systemui.keyguard.shared.model.BurnInModel
43 import com.android.systemui.keyguard.shared.model.KeyguardState
44 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
45 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
46 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
47 import com.android.systemui.keyguard.shared.model.StatusBarState
48 import com.android.systemui.keyguard.shared.model.TransitionState
49 import com.android.systemui.keyguard.shared.model.TransitionStep
50 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
51 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
52 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
53 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
54 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
55 import com.android.systemui.kosmos.testScope
56 import com.android.systemui.res.R
57 import com.android.systemui.shade.mockLargeScreenHeaderHelper
58 import com.android.systemui.shade.shadeTestUtil
59 import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
60 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
61 import com.android.systemui.testKosmos
62 import com.android.systemui.util.mockito.any
63 import com.android.systemui.util.mockito.whenever
64 import com.google.common.collect.Range
65 import com.google.common.truth.Truth.assertThat
66 import kotlinx.coroutines.ExperimentalCoroutinesApi
67 import kotlinx.coroutines.flow.MutableStateFlow
68 import kotlinx.coroutines.test.TestScope
69 import kotlinx.coroutines.test.advanceTimeBy
70 import kotlinx.coroutines.test.runCurrent
71 import kotlinx.coroutines.test.runTest
72 import org.junit.Before
73 import org.junit.Test
74 import org.junit.runner.RunWith
75 import org.mockito.Mockito.mock
76 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
77 import platform.test.runner.parameterized.Parameters
78 
79 @SmallTest
80 @RunWith(ParameterizedAndroidJunit4::class)
81 // SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
82 @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
83 class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
84 
85     companion object {
86         @JvmStatic
87         @Parameters(name = "{0}")
88         fun getParams(): List<FlagsParameterization> {
89             return FlagsParameterization.allCombinationsOf(
90                     FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX,
91                 )
92                 .andSceneContainer()
93         }
94     }
95 
96     init {
97         mSetFlagsRule.setFlagsParameterization(flags)
98     }
99 
100     val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
101     lateinit var movementFlow: MutableStateFlow<BurnInModel>
102 
103     val kosmos =
104         testKosmos().apply {
105             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
106         }
107 
108     init {
109         kosmos.aodBurnInViewModel = aodBurnInViewModel
110     }
111 
112     val testScope = kosmos.testScope
113     val configurationRepository
114         get() = kosmos.fakeConfigurationRepository
115 
116     val keyguardRepository
117         get() = kosmos.fakeKeyguardRepository
118 
119     val keyguardInteractor
120         get() = kosmos.keyguardInteractor
121 
122     val keyguardRootViewModel
123         get() = kosmos.keyguardRootViewModel
124 
125     val keyguardTransitionRepository
126         get() = kosmos.fakeKeyguardTransitionRepository
127 
128     val shadeTestUtil
129         get() = kosmos.shadeTestUtil
130 
131     val sharedNotificationContainerInteractor
132         get() = kosmos.sharedNotificationContainerInteractor
133 
134     val largeScreenHeaderHelper
135         get() = kosmos.mockLargeScreenHeaderHelper
136 
137     lateinit var underTest: SharedNotificationContainerViewModel
138 
139     @Before
140     fun setUp() {
141         overrideResource(R.bool.config_use_split_notification_shade, false)
142         movementFlow = MutableStateFlow(BurnInModel())
143         whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
144         underTest = kosmos.sharedNotificationContainerViewModel
145     }
146 
147     @Test
148     fun validateMarginStartInSplitShade() =
149         testScope.runTest {
150             overrideResource(R.bool.config_use_split_notification_shade, true)
151             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
152 
153             val dimens by collectLastValue(underTest.configurationBasedDimensions)
154 
155             configurationRepository.onAnyConfigurationChange()
156 
157             assertThat(dimens!!.marginStart).isEqualTo(0)
158         }
159 
160     @Test
161     fun validateMarginStart() =
162         testScope.runTest {
163             overrideResource(R.bool.config_use_split_notification_shade, false)
164             overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
165 
166             val dimens by collectLastValue(underTest.configurationBasedDimensions)
167 
168             configurationRepository.onAnyConfigurationChange()
169 
170             assertThat(dimens!!.marginStart).isEqualTo(20)
171         }
172 
173     @Test
174     @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
175     fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
176         testScope.runTest {
177             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
178             overrideResource(R.bool.config_use_split_notification_shade, true)
179             overrideResource(R.bool.config_use_large_screen_shade_header, true)
180             overrideResource(R.dimen.large_screen_shade_header_height, 10)
181             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
182 
183             val paddingTop by collectLastValue(underTest.paddingTopDimen)
184 
185             configurationRepository.onAnyConfigurationChange()
186 
187             // Should directly use the header height (flagged off value)
188             assertThat(paddingTop).isEqualTo(10)
189         }
190 
191     @Test
192     @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
193     fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
194         testScope.runTest {
195             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
196             overrideResource(R.bool.config_use_split_notification_shade, true)
197             overrideResource(R.bool.config_use_large_screen_shade_header, true)
198             overrideResource(R.dimen.large_screen_shade_header_height, 10)
199             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
200 
201             val paddingTop by collectLastValue(underTest.paddingTopDimen)
202             configurationRepository.onAnyConfigurationChange()
203 
204             // Should directly use the header height (flagged on value)
205             assertThat(paddingTop).isEqualTo(5)
206         }
207 
208     @Test
209     fun validatePaddingTop() =
210         testScope.runTest {
211             overrideResource(R.bool.config_use_split_notification_shade, false)
212             overrideResource(R.dimen.large_screen_shade_header_height, 10)
213             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
214 
215             val paddingTop by collectLastValue(underTest.paddingTopDimen)
216 
217             configurationRepository.onAnyConfigurationChange()
218 
219             assertThat(paddingTop).isEqualTo(0)
220         }
221 
222     @Test
223     fun validateMarginEnd() =
224         testScope.runTest {
225             overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
226 
227             val dimens by collectLastValue(underTest.configurationBasedDimensions)
228 
229             configurationRepository.onAnyConfigurationChange()
230 
231             assertThat(dimens!!.marginEnd).isEqualTo(50)
232         }
233 
234     @Test
235     fun validateMarginBottom() =
236         testScope.runTest {
237             overrideResource(R.dimen.notification_panel_margin_bottom, 50)
238 
239             val dimens by collectLastValue(underTest.configurationBasedDimensions)
240 
241             configurationRepository.onAnyConfigurationChange()
242 
243             assertThat(dimens!!.marginBottom).isEqualTo(50)
244         }
245 
246     @Test
247     @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
248     @DisableSceneContainer
249     fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
250         testScope.runTest {
251             val headerResourceHeight = 50
252             val headerHelperHeight = 100
253             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
254                 .thenReturn(headerHelperHeight)
255             overrideResource(R.bool.config_use_large_screen_shade_header, true)
256             overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
257             overrideResource(R.dimen.notification_panel_margin_top, 0)
258 
259             val dimens by collectLastValue(underTest.configurationBasedDimensions)
260 
261             configurationRepository.onAnyConfigurationChange()
262 
263             assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight)
264         }
265 
266     @Test
267     @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
268     @EnableSceneContainer
269     fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
270         testScope.runTest {
271             val headerResourceHeight = 50
272             val headerHelperHeight = 100
273             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
274                 .thenReturn(headerHelperHeight)
275             overrideResource(R.bool.config_use_large_screen_shade_header, true)
276             overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
277             overrideResource(R.dimen.notification_panel_margin_top, 0)
278 
279             val dimens by collectLastValue(underTest.configurationBasedDimensions)
280 
281             configurationRepository.onAnyConfigurationChange()
282 
283             assertThat(dimens!!.marginTop).isEqualTo(0)
284         }
285 
286     @Test
287     @DisableSceneContainer
288     @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
289     fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
290         testScope.runTest {
291             val headerResourceHeight = 50
292             val headerHelperHeight = 100
293             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
294                 .thenReturn(headerHelperHeight)
295             overrideResource(R.bool.config_use_large_screen_shade_header, true)
296             overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
297             overrideResource(R.dimen.notification_panel_margin_top, 0)
298 
299             val dimens by collectLastValue(underTest.configurationBasedDimensions)
300 
301             configurationRepository.onAnyConfigurationChange()
302 
303             assertThat(dimens!!.marginTop).isEqualTo(headerHelperHeight)
304         }
305 
306     @Test
307     @EnableSceneContainer
308     @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
309     fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
310         testScope.runTest {
311             val headerResourceHeight = 50
312             val headerHelperHeight = 100
313             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
314                 .thenReturn(headerHelperHeight)
315             overrideResource(R.bool.config_use_large_screen_shade_header, true)
316             overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
317             overrideResource(R.dimen.notification_panel_margin_top, 0)
318 
319             val dimens by collectLastValue(underTest.configurationBasedDimensions)
320 
321             configurationRepository.onAnyConfigurationChange()
322 
323             assertThat(dimens!!.marginTop).isEqualTo(0)
324         }
325 
326     @Test
327     fun glanceableHubAlpha_lockscreenToHub() =
328         testScope.runTest {
329             val alpha by collectLastValue(underTest.glanceableHubAlpha)
330 
331             // Start on lockscreen
332             showLockscreen()
333             assertThat(alpha).isEqualTo(1f)
334 
335             // Start transitioning to glanceable hub
336             val progress = 0.6f
337             keyguardTransitionRepository.sendTransitionStep(
338                 TransitionStep(
339                     transitionState = TransitionState.STARTED,
340                     from = KeyguardState.LOCKSCREEN,
341                     to = KeyguardState.GLANCEABLE_HUB,
342                     value = 0f,
343                 )
344             )
345             runCurrent()
346             keyguardTransitionRepository.sendTransitionStep(
347                 TransitionStep(
348                     transitionState = TransitionState.RUNNING,
349                     from = KeyguardState.LOCKSCREEN,
350                     to = KeyguardState.GLANCEABLE_HUB,
351                     value = progress,
352                 )
353             )
354             runCurrent()
355             assertThat(alpha).isIn(Range.closed(0f, 1f))
356 
357             // Finish transition to glanceable hub
358             keyguardTransitionRepository.sendTransitionStep(
359                 TransitionStep(
360                     transitionState = TransitionState.FINISHED,
361                     from = KeyguardState.LOCKSCREEN,
362                     to = KeyguardState.GLANCEABLE_HUB,
363                     value = 1f,
364                 )
365             )
366             assertThat(alpha).isEqualTo(0f)
367 
368             // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
369             // not fully visible.
370             shadeTestUtil.setLockscreenShadeExpansion(0.1f)
371             assertThat(alpha).isEqualTo(1f)
372         }
373 
374     @Test
375     fun glanceableHubAlpha_dreamToHub() =
376         testScope.runTest {
377             val alpha by collectLastValue(underTest.glanceableHubAlpha)
378 
379             // Start on lockscreen, notifications should be unhidden.
380             showLockscreen()
381             assertThat(alpha).isEqualTo(1f)
382 
383             // Transition to dream, notifications should be hidden so that transition
384             // from dream->hub doesn't cause notification flicker.
385             showDream()
386             assertThat(alpha).isEqualTo(0f)
387 
388             // Start transitioning to glanceable hub
389             val progress = 0.6f
390             keyguardTransitionRepository.sendTransitionStep(
391                 TransitionStep(
392                     transitionState = TransitionState.STARTED,
393                     from = KeyguardState.DREAMING,
394                     to = KeyguardState.GLANCEABLE_HUB,
395                     value = 0f,
396                 )
397             )
398             runCurrent()
399             keyguardTransitionRepository.sendTransitionStep(
400                 TransitionStep(
401                     transitionState = TransitionState.RUNNING,
402                     from = KeyguardState.DREAMING,
403                     to = KeyguardState.GLANCEABLE_HUB,
404                     value = progress,
405                 )
406             )
407             runCurrent()
408             // Keep notifications hidden during the transition from dream to hub
409             assertThat(alpha).isEqualTo(0)
410 
411             // Finish transition to glanceable hub
412             keyguardTransitionRepository.sendTransitionStep(
413                 TransitionStep(
414                     transitionState = TransitionState.FINISHED,
415                     from = KeyguardState.DREAMING,
416                     to = KeyguardState.GLANCEABLE_HUB,
417                     value = 1f,
418                 )
419             )
420             assertThat(alpha).isEqualTo(0f)
421         }
422 
423     @Test
424     fun validateMarginTop() =
425         testScope.runTest {
426             overrideResource(R.bool.config_use_large_screen_shade_header, false)
427             overrideResource(R.dimen.large_screen_shade_header_height, 50)
428             overrideResource(R.dimen.notification_panel_margin_top, 0)
429 
430             val dimens by collectLastValue(underTest.configurationBasedDimensions)
431 
432             configurationRepository.onAnyConfigurationChange()
433 
434             assertThat(dimens!!.marginTop).isEqualTo(0)
435         }
436 
437     @Test
438     fun isOnLockscreen() =
439         testScope.runTest {
440             val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
441 
442             keyguardTransitionRepository.sendTransitionSteps(
443                 from = KeyguardState.LOCKSCREEN,
444                 to = KeyguardState.GONE,
445                 testScope,
446             )
447             assertThat(isOnLockscreen).isFalse()
448 
449             // While progressing from lockscreen, should still be true
450             keyguardTransitionRepository.sendTransitionStep(
451                 TransitionStep(
452                     from = KeyguardState.LOCKSCREEN,
453                     to = KeyguardState.GONE,
454                     value = 0.8f,
455                     transitionState = TransitionState.RUNNING
456                 )
457             )
458             assertThat(isOnLockscreen).isTrue()
459 
460             keyguardTransitionRepository.sendTransitionSteps(
461                 from = KeyguardState.GONE,
462                 to = KeyguardState.LOCKSCREEN,
463                 testScope,
464             )
465             assertThat(isOnLockscreen).isTrue()
466 
467             keyguardTransitionRepository.sendTransitionSteps(
468                 from = KeyguardState.LOCKSCREEN,
469                 to = KeyguardState.PRIMARY_BOUNCER,
470                 testScope,
471             )
472             assertThat(isOnLockscreen).isTrue()
473         }
474 
475     @Test
476     fun isOnLockscreenWithoutShade() =
477         testScope.runTest {
478             val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
479 
480             // First on AOD
481             shadeTestUtil.setLockscreenShadeExpansion(0f)
482             shadeTestUtil.setQsExpansion(0f)
483             keyguardTransitionRepository.sendTransitionSteps(
484                 from = KeyguardState.LOCKSCREEN,
485                 to = KeyguardState.OCCLUDED,
486                 testScope,
487             )
488             assertThat(isOnLockscreenWithoutShade).isFalse()
489 
490             // Now move to lockscreen
491             showLockscreen()
492 
493             // While state is LOCKSCREEN, validate variations of both shade and qs expansion
494             shadeTestUtil.setQsExpansion(0f)
495             shadeTestUtil.setLockscreenShadeExpansion(0.1f)
496             assertThat(isOnLockscreenWithoutShade).isFalse()
497 
498             shadeTestUtil.setLockscreenShadeExpansion(0.1f)
499             shadeTestUtil.setShadeAndQsExpansion(0.1f, .9f)
500             assertThat(isOnLockscreenWithoutShade).isFalse()
501 
502             shadeTestUtil.setLockscreenShadeExpansion(0f)
503             shadeTestUtil.setQsExpansion(0.1f)
504             assertThat(isOnLockscreenWithoutShade).isFalse()
505 
506             shadeTestUtil.setQsExpansion(0f)
507             shadeTestUtil.setLockscreenShadeExpansion(0f)
508             assertThat(isOnLockscreenWithoutShade).isTrue()
509         }
510 
511     @Test
512     fun isOnGlanceableHubWithoutShade() =
513         testScope.runTest {
514             val isOnGlanceableHubWithoutShade by
515                 collectLastValue(underTest.isOnGlanceableHubWithoutShade)
516 
517             // Start on lockscreen
518             showLockscreen()
519             assertThat(isOnGlanceableHubWithoutShade).isFalse()
520 
521             // Move to glanceable hub
522             keyguardTransitionRepository.sendTransitionSteps(
523                 from = KeyguardState.LOCKSCREEN,
524                 to = KeyguardState.GLANCEABLE_HUB,
525                 testScope = this
526             )
527             assertThat(isOnGlanceableHubWithoutShade).isTrue()
528 
529             // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
530             shadeTestUtil.setQsExpansion(0f)
531             shadeTestUtil.setLockscreenShadeExpansion(0.1f)
532             assertThat(isOnGlanceableHubWithoutShade).isFalse()
533 
534             shadeTestUtil.setLockscreenShadeExpansion(0.1f)
535             shadeTestUtil.setShadeAndQsExpansion(0.1f, .9f)
536             assertThat(isOnGlanceableHubWithoutShade).isFalse()
537 
538             shadeTestUtil.setLockscreenShadeExpansion(0f)
539             shadeTestUtil.setQsExpansion(0.1f)
540             assertThat(isOnGlanceableHubWithoutShade).isFalse()
541 
542             shadeTestUtil.setQsExpansion(0f)
543             shadeTestUtil.setLockscreenShadeExpansion(0f)
544             assertThat(isOnGlanceableHubWithoutShade).isTrue()
545         }
546 
547     @Test
548     @DisableSceneContainer
549     fun boundsOnLockscreenNotInSplitShade() =
550         testScope.runTest {
551             val bounds by collectLastValue(underTest.bounds)
552 
553             // When not in split shade
554             overrideResource(R.bool.config_use_split_notification_shade, false)
555             configurationRepository.onAnyConfigurationChange()
556             runCurrent()
557 
558             // Start on lockscreen
559             showLockscreen()
560 
561             keyguardInteractor.setNotificationContainerBounds(
562                 NotificationContainerBounds(top = 1f, bottom = 2f)
563             )
564 
565             assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
566         }
567 
568     @Test
569     @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
570     @DisableSceneContainer
571     fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
572         testScope.runTest {
573             val bounds by collectLastValue(underTest.bounds)
574 
575             // When in split shade
576             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
577             overrideResource(R.bool.config_use_split_notification_shade, true)
578             overrideResource(R.bool.config_use_large_screen_shade_header, true)
579             overrideResource(R.dimen.large_screen_shade_header_height, 10)
580             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
581 
582             configurationRepository.onAnyConfigurationChange()
583             runCurrent()
584 
585             // Start on lockscreen
586             showLockscreen()
587 
588             keyguardInteractor.setNotificationContainerBounds(
589                 NotificationContainerBounds(top = 1f, bottom = 52f)
590             )
591             runCurrent()
592 
593             // Top should be equal to bounds (1) - padding adjustment (10)
594             assertThat(bounds)
595                 .isEqualTo(
596                     NotificationContainerBounds(
597                         top = -9f,
598                         bottom = 2f,
599                     )
600                 )
601         }
602 
603     @Test
604     @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
605     @DisableSceneContainer
606     fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
607         testScope.runTest {
608             val bounds by collectLastValue(underTest.bounds)
609 
610             // When in split shade
611             whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
612             overrideResource(R.bool.config_use_split_notification_shade, true)
613             overrideResource(R.bool.config_use_large_screen_shade_header, true)
614             overrideResource(R.dimen.large_screen_shade_header_height, 10)
615             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
616 
617             configurationRepository.onAnyConfigurationChange()
618             runCurrent()
619 
620             // Start on lockscreen
621             showLockscreen()
622 
623             keyguardInteractor.setNotificationContainerBounds(
624                 NotificationContainerBounds(top = 1f, bottom = 52f)
625             )
626             runCurrent()
627 
628             // Top should be equal to bounds (1) - padding adjustment (5)
629             assertThat(bounds).isEqualTo(NotificationContainerBounds(top = -4f, bottom = 2f))
630         }
631 
632     @Test
633     @DisableSceneContainer
634     fun boundsOnShade() =
635         testScope.runTest {
636             val bounds by collectLastValue(underTest.bounds)
637 
638             // Start on lockscreen with shade expanded
639             showLockscreenWithShadeExpanded()
640 
641             // When not in split shade
642             sharedNotificationContainerInteractor.setTopPosition(10f)
643 
644             assertThat(bounds)
645                 .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = true))
646         }
647 
648     @Test
649     @DisableSceneContainer
650     fun boundsOnQS() =
651         testScope.runTest {
652             val bounds by collectLastValue(underTest.bounds)
653 
654             // Start on lockscreen with shade expanded
655             showLockscreenWithQSExpanded()
656 
657             // When not in split shade
658             sharedNotificationContainerInteractor.setTopPosition(10f)
659 
660             assertThat(bounds)
661                 .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = false))
662         }
663 
664     @Test
665     fun maxNotificationsOnLockscreen() =
666         testScope.runTest {
667             var notificationCount = 10
668             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
669             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
670             advanceTimeBy(50L)
671             showLockscreen()
672 
673             overrideResource(R.bool.config_use_split_notification_shade, false)
674             configurationRepository.onAnyConfigurationChange()
675 
676             assertThat(maxNotifications).isEqualTo(10)
677 
678             // Also updates when directly requested (as it would from NotificationStackScrollLayout)
679             notificationCount = 25
680             sharedNotificationContainerInteractor.notificationStackChanged()
681             advanceTimeBy(50L)
682             assertThat(maxNotifications).isEqualTo(25)
683 
684             // Also ensure another collection starts with the same value. As an example, folding
685             // then unfolding will restart the coroutine and it must get the last value immediately.
686             val newMaxNotifications by
687                 collectLastValue(underTest.getMaxNotifications(calculateSpace))
688             advanceTimeBy(50L)
689             assertThat(newMaxNotifications).isEqualTo(25)
690         }
691 
692     @Test
693     fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
694         testScope.runTest {
695             var notificationCount = 10
696             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
697             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
698             advanceTimeBy(50L)
699             showLockscreen()
700 
701             overrideResource(R.bool.config_use_split_notification_shade, false)
702             configurationRepository.onAnyConfigurationChange()
703 
704             assertThat(maxNotifications).isEqualTo(10)
705 
706             // Shade expanding... still 10
707             shadeTestUtil.setLockscreenShadeExpansion(0.5f)
708             assertThat(maxNotifications).isEqualTo(10)
709 
710             notificationCount = 25
711 
712             // When shade is expanding by user interaction
713             shadeTestUtil.setLockscreenShadeTracking(true)
714 
715             // Should still be 10, since the user is interacting
716             assertThat(maxNotifications).isEqualTo(10)
717 
718             shadeTestUtil.setLockscreenShadeTracking(false)
719             shadeTestUtil.setLockscreenShadeExpansion(0f)
720 
721             // Stopped tracking, show 25
722             assertThat(maxNotifications).isEqualTo(25)
723         }
724 
725     @Test
726     fun maxNotificationsOnShade() =
727         testScope.runTest {
728             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
729             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
730             advanceTimeBy(50L)
731 
732             // Show lockscreen with shade expanded
733             showLockscreenWithShadeExpanded()
734 
735             overrideResource(R.bool.config_use_split_notification_shade, false)
736             configurationRepository.onAnyConfigurationChange()
737 
738             // -1 means No Limit
739             assertThat(maxNotifications).isEqualTo(-1)
740         }
741 
742     @Test
743     @DisableSceneContainer
744     fun translationYUpdatesOnKeyguardForBurnIn() =
745         testScope.runTest {
746             val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
747 
748             showLockscreen()
749             assertThat(translationY).isEqualTo(0)
750 
751             movementFlow.value = BurnInModel(translationY = 150)
752             assertThat(translationY).isEqualTo(150f)
753         }
754 
755     @Test
756     @DisableSceneContainer
757     fun translationYUpdatesOnKeyguard() =
758         testScope.runTest {
759             val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
760 
761             configurationRepository.setDimensionPixelSize(
762                 R.dimen.keyguard_translate_distance_on_swipe_up,
763                 -100
764             )
765             configurationRepository.onAnyConfigurationChange()
766 
767             // legacy expansion means the user is swiping up, usually for the bouncer
768             shadeTestUtil.setShadeExpansion(0.5f)
769 
770             showLockscreen()
771 
772             // The translation values are negative
773             assertThat(translationY).isLessThan(0f)
774         }
775 
776     @Test
777     @DisableSceneContainer
778     fun translationYDoesNotUpdateWhenShadeIsExpanded() =
779         testScope.runTest {
780             val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
781 
782             configurationRepository.setDimensionPixelSize(
783                 R.dimen.keyguard_translate_distance_on_swipe_up,
784                 -100
785             )
786             configurationRepository.onAnyConfigurationChange()
787 
788             // legacy expansion means the user is swiping up, usually for the bouncer but also for
789             // shade collapsing
790             shadeTestUtil.setShadeExpansion(0.5f)
791 
792             showLockscreenWithShadeExpanded()
793 
794             assertThat(translationY).isEqualTo(0f)
795         }
796 
797     @Test
798     @DisableSceneContainer
799     fun updateBounds_fromKeyguardRoot() =
800         testScope.runTest {
801             val startProgress = 0f
802             val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
803             val boundsChangingProgress = 0.2f
804             val boundsChangingStep =
805                 TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
806             val boundsInterpolatingProgress = 0.6f
807             val boundsInterpolatingStep =
808                 TransitionStep(
809                     LOCKSCREEN,
810                     AOD,
811                     boundsInterpolatingProgress,
812                     TransitionState.RUNNING
813                 )
814             val finishProgress = 1.0f
815             val finishStep =
816                 TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
817 
818             val bounds by collectLastValue(underTest.bounds)
819             val top = 123f
820             val bottom = 456f
821 
822             kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
823             runCurrent()
824             kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
825             runCurrent()
826             keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
827 
828             kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
829             runCurrent()
830             val adjustedProgress =
831                 (boundsInterpolatingProgress - boundsChangingProgress) /
832                     (1 - boundsChangingProgress)
833             val interpolatedTop = interpolate(0f, top, adjustedProgress)
834             val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
835             assertThat(bounds)
836                 .isEqualTo(
837                     NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
838                 )
839 
840             kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
841             runCurrent()
842             assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
843         }
844 
845     @Test
846     @DisableSceneContainer
847     fun updateBounds_fromGone_withoutTransitions() =
848             testScope.runTest {
849                 // Start step is already at 1.0
850                 val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING)
851                 val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED)
852 
853                 val bounds by collectLastValue(underTest.bounds)
854                 val top = 123f
855                 val bottom = 456f
856 
857                 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
858                 runCurrent()
859                 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
860                 runCurrent()
861                 keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
862                 runCurrent()
863 
864                 assertThat(bounds).isEqualTo(
865                         NotificationContainerBounds(top = top, bottom = bottom)
866                 )
867             }
868 
869     @Test
870     fun alphaOnFullQsExpansion() =
871         testScope.runTest {
872             val viewState = ViewStateAccessor()
873             val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
874 
875             showLockscreenWithQSExpanded()
876 
877             // Alpha fades out as QS expands
878             shadeTestUtil.setQsExpansion(0.5f)
879             assertThat(alpha).isWithin(0.01f).of(0.5f)
880             shadeTestUtil.setQsExpansion(0.9f)
881             assertThat(alpha).isWithin(0.01f).of(0.1f)
882 
883             // Ensure that alpha is set back to 1f when QS is fully expanded
884             shadeTestUtil.setQsExpansion(1f)
885             assertThat(alpha).isEqualTo(1f)
886         }
887 
888     @Test
889     @BrokenWithSceneContainer(330311871)
890     fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() =
891         testScope.runTest {
892             val viewState = ViewStateAccessor()
893             val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
894 
895             showLockscreen()
896             // GONE transition gets to 90% complete
897             keyguardTransitionRepository.sendTransitionStep(
898                 TransitionStep(
899                     from = KeyguardState.LOCKSCREEN,
900                     to = KeyguardState.GONE,
901                     transitionState = TransitionState.STARTED,
902                     value = 0f,
903                 )
904             )
905             runCurrent()
906             keyguardTransitionRepository.sendTransitionStep(
907                 TransitionStep(
908                     from = KeyguardState.LOCKSCREEN,
909                     to = KeyguardState.GONE,
910                     transitionState = TransitionState.RUNNING,
911                     value = 0.9f,
912                 )
913             )
914             runCurrent()
915 
916             // At this point, alpha should be zero
917             assertThat(alpha).isEqualTo(0f)
918 
919             // An attempt to override by the shade should be ignored
920             shadeTestUtil.setQsExpansion(0.5f)
921             assertThat(alpha).isEqualTo(0f)
922         }
923 
924     @Test
925     fun alphaDoesNotUpdateWhileOcclusionTransitionIsRunning() =
926         testScope.runTest {
927             val viewState = ViewStateAccessor()
928             val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
929 
930             showLockscreen()
931             // OCCLUDED transition gets to 90% complete
932             keyguardTransitionRepository.sendTransitionStep(
933                 TransitionStep(
934                     from = KeyguardState.LOCKSCREEN,
935                     to = KeyguardState.OCCLUDED,
936                     transitionState = TransitionState.STARTED,
937                     value = 0f,
938                 )
939             )
940             runCurrent()
941             keyguardTransitionRepository.sendTransitionStep(
942                 TransitionStep(
943                     from = KeyguardState.LOCKSCREEN,
944                     to = KeyguardState.OCCLUDED,
945                     transitionState = TransitionState.RUNNING,
946                     value = 0.9f,
947                 )
948             )
949             runCurrent()
950 
951             // At this point, alpha should be zero
952             assertThat(alpha).isEqualTo(0f)
953 
954             // An attempt to override by the shade should be ignored
955             shadeTestUtil.setQsExpansion(0.5f)
956             assertThat(alpha).isEqualTo(0f)
957         }
958 
959     @Test
960     fun alphaWhenGoneIsSetToOne() =
961         testScope.runTest {
962             val viewState = ViewStateAccessor()
963             val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
964 
965             showLockscreen()
966 
967             keyguardTransitionRepository.sendTransitionSteps(
968                 from = KeyguardState.LOCKSCREEN,
969                 to = KeyguardState.GONE,
970                 testScope
971             )
972             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
973 
974             assertThat(alpha).isEqualTo(1f)
975         }
976 
977     @Test
978     fun shadeCollapseFadeIn() =
979         testScope.runTest {
980             val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
981 
982             // Start on lockscreen without the shade
983             showLockscreen()
984             assertThat(fadeIn[0]).isEqualTo(false)
985 
986             // ... then the shade expands
987             showLockscreenWithShadeExpanded()
988             assertThat(fadeIn[0]).isEqualTo(false)
989 
990             // ... it collapses
991             showLockscreen()
992             assertThat(fadeIn[1]).isEqualTo(true)
993 
994             // ... and ensure the value goes back to false
995             assertThat(fadeIn[2]).isEqualTo(false)
996         }
997 
998     @Test
999     fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
1000         testScope.runTest {
1001             val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
1002 
1003             // Start on lockscreen without the shade
1004             showLockscreen()
1005             assertThat(fadeIn[0]).isEqualTo(false)
1006 
1007             // ... then the shade expands
1008             showLockscreenWithShadeExpanded()
1009             assertThat(fadeIn[0]).isEqualTo(false)
1010 
1011             // ... then user hits power to go to AOD
1012             keyguardTransitionRepository.sendTransitionSteps(
1013                 from = KeyguardState.LOCKSCREEN,
1014                 to = KeyguardState.AOD,
1015                 testScope,
1016             )
1017             // ... followed by a shade collapse
1018             showLockscreen()
1019             // ... does not trigger a fade in
1020             assertThat(fadeIn[0]).isEqualTo(false)
1021         }
1022 
1023     private suspend fun TestScope.showLockscreen() {
1024         shadeTestUtil.setQsExpansion(0f)
1025         shadeTestUtil.setLockscreenShadeExpansion(0f)
1026         runCurrent()
1027         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
1028         runCurrent()
1029         keyguardTransitionRepository.sendTransitionSteps(
1030             from = KeyguardState.AOD,
1031             to = KeyguardState.LOCKSCREEN,
1032             testScope,
1033         )
1034     }
1035 
1036     private suspend fun TestScope.showDream() {
1037         shadeTestUtil.setQsExpansion(0f)
1038         shadeTestUtil.setLockscreenShadeExpansion(0f)
1039         runCurrent()
1040         keyguardRepository.setDreaming(true)
1041         runCurrent()
1042         keyguardTransitionRepository.sendTransitionSteps(
1043             from = KeyguardState.LOCKSCREEN,
1044             to = KeyguardState.DREAMING,
1045             testScope,
1046         )
1047     }
1048 
1049     private suspend fun TestScope.showLockscreenWithShadeExpanded() {
1050         shadeTestUtil.setQsExpansion(0f)
1051         shadeTestUtil.setLockscreenShadeExpansion(1f)
1052         runCurrent()
1053         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
1054         runCurrent()
1055         keyguardTransitionRepository.sendTransitionSteps(
1056             from = KeyguardState.AOD,
1057             to = KeyguardState.LOCKSCREEN,
1058             testScope,
1059         )
1060     }
1061 
1062     private suspend fun TestScope.showLockscreenWithQSExpanded() {
1063         shadeTestUtil.setLockscreenShadeExpansion(0f)
1064         shadeTestUtil.setQsExpansion(1f)
1065         runCurrent()
1066         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
1067         runCurrent()
1068         keyguardTransitionRepository.sendTransitionSteps(
1069             from = KeyguardState.AOD,
1070             to = KeyguardState.LOCKSCREEN,
1071             testScope,
1072         )
1073     }
1074 }
1075