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 
18 package com.android.systemui.keyguard.domain.interactor
19 
20 import androidx.test.ext.junit.runners.AndroidJUnit4
21 import androidx.test.filters.SmallTest
22 import com.android.systemui.SysuiTestCase
23 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
24 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
25 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
26 import com.android.systemui.coroutines.collectLastValue
27 import com.android.systemui.doze.util.BurnInHelperWrapper
28 import com.android.systemui.flags.FakeFeatureFlags
29 import com.android.systemui.flags.Flags
30 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
31 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
32 import com.android.systemui.keyguard.shared.model.StatusBarState
33 import com.android.systemui.keyguard.shared.model.WakeSleepReason
34 import com.android.systemui.keyguard.shared.model.WakefulnessModel
35 import com.android.systemui.keyguard.shared.model.WakefulnessState
36 import com.android.systemui.shade.data.repository.FakeShadeRepository
37 import com.android.systemui.statusbar.phone.SystemUIDialogManager
38 import com.android.systemui.util.mockito.argumentCaptor
39 import com.android.systemui.util.mockito.eq
40 import com.android.systemui.util.mockito.whenever
41 import com.google.common.truth.Truth.assertThat
42 import kotlinx.coroutines.ExperimentalCoroutinesApi
43 import kotlinx.coroutines.test.TestScope
44 import kotlinx.coroutines.test.runCurrent
45 import kotlinx.coroutines.test.runTest
46 import org.junit.Before
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 import org.mockito.ArgumentMatchers.anyInt
50 import org.mockito.Mock
51 import org.mockito.Mockito.verify
52 import org.mockito.MockitoAnnotations
53 
54 @ExperimentalCoroutinesApi
55 @SmallTest
56 @RunWith(AndroidJUnit4::class)
57 class UdfpsKeyguardInteractorTest : SysuiTestCase() {
58     private val burnInProgress = 1f
59     private val burnInYOffset = 20
60     private val burnInXOffset = 10
61 
62     private lateinit var testScope: TestScope
63     private lateinit var configRepository: FakeConfigurationRepository
64     private lateinit var bouncerRepository: KeyguardBouncerRepository
65     private lateinit var keyguardRepository: FakeKeyguardRepository
66     private lateinit var fakeCommandQueue: FakeCommandQueue
67     private lateinit var featureFlags: FakeFeatureFlags
68     private lateinit var burnInInteractor: BurnInInteractor
69     private lateinit var shadeRepository: FakeShadeRepository
70     private lateinit var keyguardInteractor: KeyguardInteractor
71 
72     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
73     @Mock private lateinit var dialogManager: SystemUIDialogManager
74 
75     private lateinit var underTest: UdfpsKeyguardInteractor
76 
77     @Before
78     fun setUp() {
79         MockitoAnnotations.initMocks(this)
80         testScope = TestScope()
81         configRepository = FakeConfigurationRepository()
82         featureFlags =
83             FakeFeatureFlags().apply {
84                 set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
85                 set(Flags.FACE_AUTH_REFACTOR, false)
86             }
87         KeyguardInteractorFactory.create(featureFlags = featureFlags).let {
88             keyguardInteractor = it.keyguardInteractor
89             keyguardRepository = it.repository
90         }
91         bouncerRepository = FakeKeyguardBouncerRepository()
92         shadeRepository = FakeShadeRepository()
93         fakeCommandQueue = FakeCommandQueue()
94         burnInInteractor =
95             BurnInInteractor(
96                 context,
97                 burnInHelper,
98                 testScope.backgroundScope,
99                 configRepository,
100                 keyguardInteractor
101             )
102 
103         underTest =
104             UdfpsKeyguardInteractor(
105                 configRepository,
106                 burnInInteractor,
107                 keyguardInteractor,
108                 shadeRepository,
109                 dialogManager,
110             )
111     }
112 
113     @Test
114     fun dozeChanges_updatesUdfpsAodModel() =
115         testScope.runTest {
116             val burnInOffsets by collectLastValue(underTest.burnInOffsets)
117             initializeBurnInOffsets()
118 
119             // WHEN we're not dozing
120             setAwake()
121             runCurrent()
122 
123             // THEN burn in offsets are 0
124             assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
125             assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
126             assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
127 
128             // WHEN we're in the middle of the doze amount change
129             keyguardRepository.setDozeAmount(.50f)
130             runCurrent()
131 
132             // THEN burn in is updated (between 0 and the full offset)
133             assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
134             assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
135             assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
136             assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
137             assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
138             assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
139 
140             // WHEN we're fully dozing
141             keyguardRepository.setDozeAmount(1f)
142             runCurrent()
143 
144             // THEN burn in offsets are updated to final current values (for the given time)
145             assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
146             assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
147             assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
148         }
149 
150     @Test
151     fun dialogHideAffordances() =
152         testScope.runTest {
153             val dialogHideAffordancesRequest by
154                 collectLastValue(underTest.dialogHideAffordancesRequest)
155             runCurrent()
156             val captor = argumentCaptor<SystemUIDialogManager.Listener>()
157             verify(dialogManager).registerListener(captor.capture())
158 
159             captor.value.shouldHideAffordances(false)
160             assertThat(dialogHideAffordancesRequest).isEqualTo(false)
161 
162             captor.value.shouldHideAffordances(true)
163             assertThat(dialogHideAffordancesRequest).isEqualTo(true)
164 
165             captor.value.shouldHideAffordances(false)
166             assertThat(dialogHideAffordancesRequest).isEqualTo(false)
167         }
168 
169     @Test
170     fun shadeExpansion_updates() =
171         testScope.runTest {
172             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
173             val shadeExpansion by collectLastValue(underTest.shadeExpansion)
174             assertThat(shadeExpansion).isEqualTo(0f)
175 
176             shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
177             assertThat(shadeExpansion).isEqualTo(.5f)
178 
179             shadeRepository.setUdfpsTransitionToFullShadeProgress(.7f)
180             assertThat(shadeExpansion).isEqualTo(.7f)
181 
182             shadeRepository.setUdfpsTransitionToFullShadeProgress(.22f)
183             assertThat(shadeExpansion).isEqualTo(.22f)
184 
185             keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
186             assertThat(shadeExpansion).isEqualTo(1f)
187         }
188 
189     @Test
190     fun qsProgress_updates() =
191         testScope.runTest {
192             val qsProgress by collectLastValue(underTest.qsProgress)
193             assertThat(qsProgress).isEqualTo(0f)
194 
195             shadeRepository.setQsExpansion(.22f)
196             assertThat(qsProgress).isEqualTo(.44f)
197 
198             shadeRepository.setQsExpansion(.5f)
199             assertThat(qsProgress).isEqualTo(1f)
200 
201             shadeRepository.setQsExpansion(.7f)
202             assertThat(qsProgress).isEqualTo(1f)
203         }
204 
205     private fun initializeBurnInOffsets() {
206         whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
207         whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
208             .thenReturn(burnInXOffset)
209         whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
210             .thenReturn(burnInYOffset)
211     }
212 
213     private fun setAwake() {
214         keyguardRepository.setDozeAmount(0f)
215         keyguardRepository.dozeTimeTick()
216 
217         bouncerRepository.setAlternateVisible(false)
218         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
219         bouncerRepository.setPrimaryShow(false)
220         keyguardRepository.setWakefulnessModel(
221             WakefulnessModel(
222                 WakefulnessState.AWAKE,
223                 WakeSleepReason.POWER_BUTTON,
224                 WakeSleepReason.POWER_BUTTON,
225             )
226         )
227     }
228 }
229