1 /*
<lambda>null2  * Copyright (C) 2024 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.shade.domain.startable
18 
19 import android.platform.test.annotations.DisableFlags
20 import android.platform.test.annotations.EnableFlags
21 import android.platform.test.flag.junit.FlagsParameterization
22 import androidx.test.filters.SmallTest
23 import com.android.compose.animation.scene.ObservableTransitionState
24 import com.android.compose.animation.scene.SceneKey
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
27 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
28 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
29 import com.android.systemui.coroutines.collectLastValue
30 import com.android.systemui.display.data.repository.displayStateRepository
31 import com.android.systemui.flags.EnableSceneContainer
32 import com.android.systemui.flags.parameterizeSceneContainerFlag
33 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
34 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
35 import com.android.systemui.kosmos.testScope
36 import com.android.systemui.res.R
37 import com.android.systemui.scene.domain.interactor.sceneInteractor
38 import com.android.systemui.scene.shared.model.Scenes
39 import com.android.systemui.scene.shared.model.fakeSceneDataSource
40 import com.android.systemui.shade.ShadeExpansionChangeEvent
41 import com.android.systemui.shade.ShadeExpansionListener
42 import com.android.systemui.shade.domain.interactor.shadeInteractor
43 import com.android.systemui.shade.shared.flag.DualShade
44 import com.android.systemui.shade.shared.model.ShadeMode
45 import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
46 import com.android.systemui.statusbar.phone.scrimController
47 import com.android.systemui.testKosmos
48 import com.android.systemui.util.mockito.any
49 import com.android.systemui.util.mockito.mock
50 import com.android.systemui.util.mockito.whenever
51 import com.google.common.truth.Truth.assertThat
52 import kotlinx.coroutines.ExperimentalCoroutinesApi
53 import kotlinx.coroutines.flow.MutableStateFlow
54 import kotlinx.coroutines.flow.flowOf
55 import kotlinx.coroutines.test.TestScope
56 import kotlinx.coroutines.test.runCurrent
57 import kotlinx.coroutines.test.runTest
58 import org.junit.Test
59 import org.junit.runner.RunWith
60 import org.mockito.kotlin.verify
61 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
62 import platform.test.runner.parameterized.Parameters
63 
64 @OptIn(ExperimentalCoroutinesApi::class)
65 @SmallTest
66 @RunWith(ParameterizedAndroidJunit4::class)
67 class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
68     private val kosmos = testKosmos()
69     private val testScope = kosmos.testScope
70     private val shadeInteractor by lazy { kosmos.shadeInteractor }
71     private val sceneInteractor by lazy { kosmos.sceneInteractor }
72     private val shadeExpansionStateManager by lazy { kosmos.shadeExpansionStateManager }
73     private val fakeConfigurationRepository by lazy { kosmos.fakeConfigurationRepository }
74     private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
75 
76     private val underTest: ShadeStartable = kosmos.shadeStartable
77 
78     companion object {
79         @JvmStatic
80         @Parameters(name = "{0}")
81         fun getParams(): List<FlagsParameterization> {
82             return parameterizeSceneContainerFlag()
83         }
84     }
85 
86     init {
87         mSetFlagsRule.setFlagsParameterization(flags)
88     }
89 
90     @Test
91     @DisableFlags(DualShade.FLAG_NAME)
92     fun hydrateShadeMode_dualShadeDisabled() =
93         testScope.runTest {
94             overrideResource(R.bool.config_use_split_notification_shade, false)
95             val shadeMode by collectLastValue(shadeInteractor.shadeMode)
96 
97             underTest.start()
98             assertThat(shadeMode).isEqualTo(ShadeMode.Single)
99 
100             overrideResource(R.bool.config_use_split_notification_shade, true)
101             fakeConfigurationRepository.onAnyConfigurationChange()
102             assertThat(shadeMode).isEqualTo(ShadeMode.Split)
103 
104             overrideResource(R.bool.config_use_split_notification_shade, false)
105             fakeConfigurationRepository.onAnyConfigurationChange()
106             assertThat(shadeMode).isEqualTo(ShadeMode.Single)
107         }
108 
109     @Test
110     @EnableFlags(DualShade.FLAG_NAME)
111     fun hydrateShadeMode_dualShadeEnabled() =
112         testScope.runTest {
113             overrideResource(R.bool.config_use_split_notification_shade, false)
114             val shadeMode by collectLastValue(shadeInteractor.shadeMode)
115 
116             underTest.start()
117             assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
118 
119             overrideResource(R.bool.config_use_split_notification_shade, true)
120             fakeConfigurationRepository.onAnyConfigurationChange()
121             assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
122 
123             overrideResource(R.bool.config_use_split_notification_shade, false)
124             fakeConfigurationRepository.onAnyConfigurationChange()
125             assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
126         }
127 
128     @Test
129     @EnableSceneContainer
130     fun hydrateShadeExpansionStateManager() =
131         testScope.runTest {
132             val expansionListener = mock<ShadeExpansionListener>()
133             var latestChangeEvent: ShadeExpansionChangeEvent? = null
134             whenever(expansionListener.onPanelExpansionChanged(any())).thenAnswer {
135                 latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
136                 Unit
137             }
138             shadeExpansionStateManager.addExpansionListener(expansionListener)
139 
140             underTest.start()
141 
142             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
143                 AuthenticationMethodModel.Pin
144             )
145             runCurrent()
146             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
147                 SuccessFingerprintAuthenticationStatus(0, true)
148             )
149             runCurrent()
150             val transitionState =
151                 MutableStateFlow<ObservableTransitionState>(
152                     ObservableTransitionState.Idle(Scenes.Gone)
153                 )
154             sceneInteractor.setTransitionState(transitionState)
155 
156             changeScene(Scenes.Gone, transitionState)
157             val currentScene by collectLastValue(sceneInteractor.currentScene)
158             assertThat(currentScene).isEqualTo(Scenes.Gone)
159 
160             assertThat(latestChangeEvent)
161                 .isEqualTo(
162                     ShadeExpansionChangeEvent(
163                         fraction = 0f,
164                         expanded = false,
165                         tracking = false,
166                     )
167                 )
168 
169             changeScene(Scenes.Shade, transitionState) { progress ->
170                 assertThat(latestChangeEvent?.fraction).isEqualTo(progress)
171             }
172         }
173 
174     @Test
175     @EnableSceneContainer
176     fun hydrateFullWidth() =
177         testScope.runTest {
178             underTest.start()
179 
180             kosmos.displayStateRepository.setIsLargeScreen(true)
181             runCurrent()
182             verify(kosmos.notificationStackScrollLayoutController).setIsFullWidth(false)
183             assertThat(kosmos.scrimController.clipQsScrim).isFalse()
184         }
185 
186     private fun TestScope.changeScene(
187         toScene: SceneKey,
188         transitionState: MutableStateFlow<ObservableTransitionState>,
189         assertDuringProgress: ((progress: Float) -> Unit) = {},
190     ) {
191         val currentScene by collectLastValue(sceneInteractor.currentScene)
192         val progressFlow = MutableStateFlow(0f)
193         transitionState.value =
194             ObservableTransitionState.Transition(
195                 fromScene = checkNotNull(currentScene),
196                 toScene = toScene,
197                 currentScene = flowOf(checkNotNull(currentScene)),
198                 progress = progressFlow,
199                 isInitiatedByUserInput = true,
200                 isUserInputOngoing = flowOf(true),
201             )
202         runCurrent()
203         assertDuringProgress(progressFlow.value)
204 
205         progressFlow.value = 0.2f
206         runCurrent()
207         assertDuringProgress(progressFlow.value)
208 
209         progressFlow.value = 0.6f
210         runCurrent()
211         assertDuringProgress(progressFlow.value)
212 
213         progressFlow.value = 1f
214         runCurrent()
215         assertDuringProgress(progressFlow.value)
216 
217         transitionState.value = ObservableTransitionState.Idle(toScene)
218         fakeSceneDataSource.changeScene(toScene)
219         runCurrent()
220         assertDuringProgress(progressFlow.value)
221 
222         assertThat(currentScene).isEqualTo(toScene)
223     }
224 }
225