• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.keyguard.ui.viewmodel
18 
19 import android.platform.test.flag.junit.FlagsParameterization
20 import androidx.compose.ui.Alignment
21 import androidx.test.filters.SmallTest
22 import com.android.systemui.SysuiTestCase
23 import com.android.systemui.biometrics.authController
24 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
25 import com.android.systemui.flags.DisableSceneContainer
26 import com.android.systemui.flags.EnableSceneContainer
27 import com.android.systemui.flags.andSceneContainer
28 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
29 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
30 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
31 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
32 import com.android.systemui.keyguard.shared.model.ClockSize
33 import com.android.systemui.keyguard.shared.model.KeyguardState
34 import com.android.systemui.keyguard.shared.transition.fakeKeyguardTransitionAnimationCallback
35 import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
36 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BelowClock
37 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel.NotificationsPlacement.BesideClock
38 import com.android.systemui.kosmos.Kosmos
39 import com.android.systemui.kosmos.collectLastValue
40 import com.android.systemui.kosmos.runCurrent
41 import com.android.systemui.kosmos.runTest
42 import com.android.systemui.kosmos.testScope
43 import com.android.systemui.lifecycle.activateIn
44 import com.android.systemui.res.R
45 import com.android.systemui.scene.domain.interactor.sceneInteractor
46 import com.android.systemui.scene.shared.model.Scenes
47 import com.android.systemui.shade.data.repository.shadeRepository
48 import com.android.systemui.shade.domain.interactor.enableDualShade
49 import com.android.systemui.shade.domain.interactor.enableSingleShade
50 import com.android.systemui.shade.domain.interactor.enableSplitShade
51 import com.android.systemui.shade.domain.interactor.shadeModeInteractor
52 import com.android.systemui.shade.shared.model.ShadeMode
53 import com.android.systemui.testKosmos
54 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
55 import com.android.systemui.util.mockito.whenever
56 import com.google.common.truth.Truth.assertThat
57 import java.util.Locale
58 import kotlinx.coroutines.Job
59 import org.junit.Before
60 import org.junit.Test
61 import org.junit.runner.RunWith
62 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
63 import platform.test.runner.parameterized.Parameters
64 
65 @SmallTest
66 @RunWith(ParameterizedAndroidJunit4::class)
67 class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
68 
69     private val kosmos: Kosmos = testKosmos()
70 
71     private lateinit var underTest: LockscreenContentViewModel
72     private val activationJob = Job()
73 
74     companion object {
75         @JvmStatic
76         @Parameters(name = "{0}")
77         fun getParams(): List<FlagsParameterization> {
78             return FlagsParameterization.allCombinationsOf().andSceneContainer()
79         }
80     }
81 
82     init {
83         mSetFlagsRule.setFlagsParameterization(flags)
84     }
85 
86     @Before
87     fun setup() {
88         with(kosmos) {
89             shadeRepository.setShadeLayoutWide(false)
90             underTest =
91                 lockscreenContentViewModelFactory.create(fakeKeyguardTransitionAnimationCallback)
92             underTest.activateIn(testScope, activationJob)
93         }
94     }
95 
96     @Test
97     fun isUdfpsVisible_withUdfps_true() =
98         kosmos.runTest {
99             whenever(authController.isUdfpsSupported).thenReturn(true)
100             assertThat(underTest.isUdfpsVisible).isTrue()
101         }
102 
103     @Test
104     fun isUdfpsVisible_withoutUdfps_false() =
105         kosmos.runTest {
106             whenever(authController.isUdfpsSupported).thenReturn(false)
107             assertThat(underTest.isUdfpsVisible).isFalse()
108         }
109 
110     @Test
111     fun notificationsPlacement_splitShade_topEnd() =
112         kosmos.runTest {
113             setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.SMALL)
114 
115             assertThat(underTest.notificationsPlacement)
116                 .isEqualTo(BesideClock(alignment = Alignment.TopEnd))
117         }
118 
119     @Test
120     fun notificationsPlacement_singleShade_below() =
121         kosmos.runTest {
122             setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL)
123 
124             assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock)
125         }
126 
127     @Test
128     @EnableSceneContainer
129     fun notificationsPlacement_dualShadeSmallClock_below() =
130         kosmos.runTest {
131             setupState(
132                 shadeMode = ShadeMode.Dual,
133                 clockSize = ClockSize.SMALL,
134                 shadeLayoutWide = true,
135             )
136 
137             assertThat(underTest.notificationsPlacement).isEqualTo(BelowClock)
138         }
139 
140     @Test
141     @EnableSceneContainer
142     fun notificationsPlacement_dualShadeLargeClock_topStart() =
143         kosmos.runTest {
144             setupState(
145                 shadeMode = ShadeMode.Dual,
146                 clockSize = ClockSize.LARGE,
147                 shadeLayoutWide = true,
148             )
149 
150             assertThat(underTest.notificationsPlacement)
151                 .isEqualTo(BesideClock(alignment = Alignment.TopStart))
152         }
153 
154     @Test
155     fun areNotificationsVisible_splitShadeTrue_true() =
156         kosmos.runTest {
157             setupState(shadeMode = ShadeMode.Split, clockSize = ClockSize.LARGE)
158 
159             assertThat(underTest.areNotificationsVisible).isTrue()
160         }
161 
162     @Test
163     @EnableSceneContainer
164     fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
165         kosmos.runTest {
166             setupState(
167                 shadeMode = ShadeMode.Dual,
168                 clockSize = ClockSize.LARGE,
169                 shadeLayoutWide = true,
170             )
171 
172             assertThat(underTest.areNotificationsVisible).isTrue()
173         }
174 
175     @Test
176     @DisableSceneContainer
177     fun areNotificationsVisible_withSmallClock_true() =
178         kosmos.runTest {
179             setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.SMALL)
180 
181             assertThat(underTest.areNotificationsVisible).isTrue()
182         }
183 
184     @Test
185     @DisableSceneContainer
186     fun areNotificationsVisible_withLargeClock_false() =
187         kosmos.runTest {
188             setupState(shadeMode = ShadeMode.Single, clockSize = ClockSize.LARGE)
189 
190             assertThat(underTest.areNotificationsVisible).isFalse()
191         }
192 
193     @Test
194     fun unfoldTranslations() =
195         kosmos.runTest {
196             val maxTranslation = prepareConfiguration()
197 
198             val unfoldProvider = fakeUnfoldTransitionProgressProvider
199             unfoldProvider.onTransitionStarted()
200             runCurrent()
201             assertThat(underTest.unfoldTranslations.start).isZero()
202             assertThat(underTest.unfoldTranslations.end).isZero()
203 
204             repeat(10) { repetition ->
205                 val transitionProgress = 0.1f * (repetition + 1)
206                 unfoldProvider.onTransitionProgress(transitionProgress)
207                 runCurrent()
208                 assertThat(underTest.unfoldTranslations.start)
209                     .isEqualTo((1 - transitionProgress) * maxTranslation)
210                 assertThat(underTest.unfoldTranslations.end)
211                     .isEqualTo(-(1 - transitionProgress) * maxTranslation)
212             }
213 
214             unfoldProvider.onTransitionFinishing()
215             runCurrent()
216             assertThat(underTest.unfoldTranslations.start).isZero()
217             assertThat(underTest.unfoldTranslations.end).isZero()
218 
219             unfoldProvider.onTransitionFinished()
220             runCurrent()
221             assertThat(underTest.unfoldTranslations.start).isZero()
222             assertThat(underTest.unfoldTranslations.end).isZero()
223         }
224 
225     @Test
226     fun isContentVisible_whenNotOccluded_visible() =
227         kosmos.runTest {
228             keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
229             runCurrent()
230             assertThat(underTest.isContentVisible).isTrue()
231         }
232 
233     @Test
234     fun isContentVisible_whenOccluded_notVisible() =
235         kosmos.runTest {
236             keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
237             fakeKeyguardTransitionRepository.transitionTo(
238                 KeyguardState.LOCKSCREEN,
239                 KeyguardState.OCCLUDED,
240             )
241             runCurrent()
242             assertThat(underTest.isContentVisible).isFalse()
243         }
244 
245     @Test
246     fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() =
247         kosmos.runTest {
248             keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
249             fakeKeyguardTransitionRepository.transitionTo(
250                 KeyguardState.LOCKSCREEN,
251                 KeyguardState.OCCLUDED,
252             )
253             runCurrent()
254 
255             sceneInteractor.snapToScene(Scenes.Shade, "")
256             runCurrent()
257             assertThat(underTest.isContentVisible).isFalse()
258         }
259 
260     @Test
261     fun activate_setsDelegate_onKeyguardTransitionAnimationCallbackDelegator() =
262         kosmos.runTest {
263             runCurrent()
264             assertThat(keyguardTransitionAnimationCallbackDelegator.delegate)
265                 .isSameInstanceAs(fakeKeyguardTransitionAnimationCallback)
266         }
267 
268     @Test
269     fun deactivate_clearsDelegate_onKeyguardTransitionAnimationCallbackDelegator() =
270         kosmos.runTest {
271             activationJob.cancel()
272             runCurrent()
273             assertThat(keyguardTransitionAnimationCallbackDelegator.delegate).isNull()
274         }
275 
276     @Test
277     fun isContentVisible_whenOccluded_notVisibleInOccluded_visibleInAod() =
278         kosmos.runTest {
279             keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
280             fakeKeyguardTransitionRepository.transitionTo(
281                 from = KeyguardState.LOCKSCREEN,
282                 to = KeyguardState.OCCLUDED,
283             )
284             runCurrent()
285 
286             sceneInteractor.snapToScene(Scenes.Shade, "")
287             runCurrent()
288             assertThat(underTest.isContentVisible).isFalse()
289 
290             fakeKeyguardTransitionRepository.transitionTo(KeyguardState.OCCLUDED, KeyguardState.AOD)
291             runCurrent()
292 
293             sceneInteractor.snapToScene(Scenes.Lockscreen, "")
294             runCurrent()
295 
296             assertThat(underTest.isContentVisible).isTrue()
297         }
298 
299     private fun Kosmos.setupState(
300         shadeMode: ShadeMode,
301         clockSize: ClockSize,
302         shadeLayoutWide: Boolean? = null,
303     ) {
304         val isShadeLayoutWide by collectLastValue(kosmos.shadeRepository.isShadeLayoutWide)
305         val collectedClockSize by collectLastValue(kosmos.keyguardClockInteractor.clockSize)
306         val collectedShadeMode by collectLastValue(kosmos.shadeModeInteractor.shadeMode)
307         when (shadeMode) {
308             ShadeMode.Dual -> kosmos.enableDualShade(wideLayout = shadeLayoutWide)
309             ShadeMode.Single -> kosmos.enableSingleShade()
310             ShadeMode.Split -> kosmos.enableSplitShade()
311         }
312         fakeKeyguardClockRepository.setShouldForceSmallClock(clockSize == ClockSize.SMALL)
313         fakeKeyguardClockRepository.setClockSize(clockSize)
314         runCurrent()
315         if (shadeLayoutWide != null) {
316             assertThat(isShadeLayoutWide).isEqualTo(shadeLayoutWide)
317         }
318         assertThat(collectedShadeMode).isEqualTo(shadeMode)
319         assertThat(collectedClockSize).isEqualTo(clockSize)
320     }
321 
322     private fun prepareConfiguration(): Int {
323         val configuration = context.resources.configuration
324         configuration.setLayoutDirection(Locale.US)
325         kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
326         val maxTranslation = 10
327         kosmos.fakeConfigurationRepository.setDimensionPixelSize(
328             R.dimen.notification_side_paddings,
329             maxTranslation,
330         )
331         return maxTranslation
332     }
333 }
334