1 /*
<lambda>null2  * Copyright (C) 2021 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.biometrics
18 
19 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
20 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
21 import android.hardware.biometrics.BiometricRequestConstants.RequestReason
22 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
23 import android.testing.TestableLooper.RunWithLooper
24 import android.view.LayoutInflater
25 import android.view.MotionEvent
26 import android.view.View
27 import android.view.WindowManager
28 import android.view.accessibility.AccessibilityManager
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import com.android.app.viewcapture.ViewCapture
32 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
33 import com.android.keyguard.KeyguardUpdateMonitor
34 import com.android.systemui.SysuiTestCase
35 import com.android.systemui.animation.ActivityTransitionAnimator
36 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
37 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
38 import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
39 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
40 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
41 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
42 import com.android.systemui.dump.DumpManager
43 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
44 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
45 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
46 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
47 import com.android.systemui.keyguard.shared.model.KeyguardState
48 import com.android.systemui.kosmos.testScope
49 import com.android.systemui.plugins.statusbar.StatusBarStateController
50 import com.android.systemui.power.data.repository.FakePowerRepository
51 import com.android.systemui.power.data.repository.fakePowerRepository
52 import com.android.systemui.power.domain.interactor.PowerInteractor
53 import com.android.systemui.power.domain.interactor.powerInteractor
54 import com.android.systemui.power.shared.model.WakeSleepReason
55 import com.android.systemui.power.shared.model.WakefulnessState
56 import com.android.systemui.shade.domain.interactor.ShadeInteractor
57 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
58 import com.android.systemui.statusbar.phone.SystemUIDialogManager
59 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
60 import com.android.systemui.statusbar.policy.ConfigurationController
61 import com.android.systemui.statusbar.policy.KeyguardStateController
62 import com.android.systemui.testKosmos
63 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
64 import com.google.common.truth.Truth.assertThat
65 import dagger.Lazy
66 import kotlinx.coroutines.ExperimentalCoroutinesApi
67 import kotlinx.coroutines.test.TestScope
68 import kotlinx.coroutines.test.runCurrent
69 import kotlinx.coroutines.test.runTest
70 import org.junit.Before
71 import org.junit.Rule
72 import org.junit.Test
73 import org.junit.runner.RunWith
74 import org.mockito.ArgumentCaptor
75 import org.mockito.ArgumentMatchers.any
76 import org.mockito.ArgumentMatchers.eq
77 import org.mockito.Captor
78 import org.mockito.Mock
79 import org.mockito.Mockito.never
80 import org.mockito.Mockito.verify
81 import org.mockito.Mockito.`when` as whenever
82 import org.mockito.junit.MockitoJUnit
83 
84 private const val REQUEST_ID = 2L
85 
86 // Dimensions for the current display resolution.
87 private const val DISPLAY_WIDTH = 1080
88 private const val DISPLAY_HEIGHT = 1920
89 private const val SENSOR_WIDTH = 30
90 private const val SENSOR_HEIGHT = 60
91 
92 @ExperimentalCoroutinesApi
93 @SmallTest
94 @RunWith(AndroidJUnit4::class)
95 @RunWithLooper(setAsMainLooper = true)
96 class UdfpsControllerOverlayTest : SysuiTestCase() {
97     private val kosmos = testKosmos()
98 
99     @JvmField @Rule var rule = MockitoJUnit.rule()
100 
101     @Mock private lateinit var inflater: LayoutInflater
102     @Mock private lateinit var windowManager: WindowManager
103     @Mock private lateinit var lazyViewCapture: kotlin.Lazy<ViewCapture>
104     @Mock private lateinit var accessibilityManager: AccessibilityManager
105     @Mock private lateinit var statusBarStateController: StatusBarStateController
106     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
107     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
108     @Mock private lateinit var dialogManager: SystemUIDialogManager
109     @Mock private lateinit var dumpManager: DumpManager
110     @Mock private lateinit var configurationController: ConfigurationController
111     @Mock private lateinit var keyguardStateController: KeyguardStateController
112     @Mock
113     private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
114     @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
115     @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
116     @Mock private lateinit var udfpsController: UdfpsController
117     @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
118     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
119     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
120     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
121     @Mock
122     private lateinit var deviceEntryUdfpsTouchOverlayViewModel:
123         DeviceEntryUdfpsTouchOverlayViewModel
124     @Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
125     @Mock
126     private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
127     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
128     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
129     @Mock private lateinit var shadeInteractor: ShadeInteractor
130     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
131     @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
132     private lateinit var powerRepository: FakePowerRepository
133     private lateinit var powerInteractor: PowerInteractor
134     private lateinit var testScope: TestScope
135 
136     private val onTouch = { _: View, _: MotionEvent -> true }
137     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
138     private lateinit var controllerOverlay: UdfpsControllerOverlay
139 
140     @Before
141     fun setup() {
142         testScope = kosmos.testScope
143         powerRepository = kosmos.fakePowerRepository
144         powerInteractor = kosmos.powerInteractor
145         keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
146         keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
147     }
148 
149     private suspend fun withReasonSuspend(
150         @RequestReason reason: Int,
151         isDebuggable: Boolean = false,
152         block: suspend () -> Unit,
153     ) {
154         withReason(reason, isDebuggable)
155         block()
156     }
157 
158     private fun withReason(
159         @RequestReason reason: Int,
160         isDebuggable: Boolean = false,
161         block: () -> Unit = {},
162     ) {
163         controllerOverlay =
164             UdfpsControllerOverlay(
165                 context,
166                 inflater,
167                 ViewCaptureAwareWindowManager(
168                     windowManager,
169                     lazyViewCapture,
170                     isViewCaptureEnabled = false,
171                 ),
172                 accessibilityManager,
173                 statusBarStateController,
174                 statusBarKeyguardViewManager,
175                 keyguardUpdateMonitor,
176                 dialogManager,
177                 dumpManager,
178                 configurationController,
179                 keyguardStateController,
180                 unlockedScreenOffAnimationController,
181                 udfpsDisplayMode,
182                 REQUEST_ID,
183                 reason,
184                 controllerCallback,
185                 onTouch,
186                 mActivityTransitionAnimator,
187                 primaryBouncerInteractor,
188                 alternateBouncerInteractor,
189                 isDebuggable,
190                 udfpsKeyguardAccessibilityDelegate,
191                 keyguardTransitionInteractor,
192                 mSelectedUserInteractor,
193                 { deviceEntryUdfpsTouchOverlayViewModel },
194                 { defaultUdfpsTouchOverlayViewModel },
195                 shadeInteractor,
196                 udfpsOverlayInteractor,
197                 powerInteractor,
198                 testScope,
199             )
200         block()
201     }
202 
203     @Test
204     fun showUdfpsOverlay_whileGoingToSleep() =
205         testScope.runTest {
206             withReasonSuspend(REASON_AUTH_KEYGUARD) {
207                 keyguardTransitionRepository.sendTransitionSteps(
208                     from = KeyguardState.OFF,
209                     to = KeyguardState.GONE,
210                     testScope = this,
211                 )
212                 powerRepository.updateWakefulness(
213                     rawState = WakefulnessState.STARTING_TO_SLEEP,
214                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
215                     lastSleepReason = WakeSleepReason.OTHER,
216                 )
217                 runCurrent()
218 
219                 // WHEN a request comes to show the view
220                 controllerOverlay.show(udfpsController, overlayParams)
221                 runCurrent()
222 
223                 // THEN the view does not get added immediately
224                 verify(windowManager, never()).addView(any(), any())
225 
226                 // we hide to end the job that listens for the finishedGoingToSleep signal
227                 controllerOverlay.hide()
228             }
229         }
230 
231     @Test
232     fun showUdfpsOverlay_whileAsleep() =
233         testScope.runTest {
234             withReasonSuspend(REASON_AUTH_KEYGUARD) {
235                 keyguardTransitionRepository.sendTransitionSteps(
236                     from = KeyguardState.OFF,
237                     to = KeyguardState.GONE,
238                     testScope = this,
239                 )
240                 powerRepository.updateWakefulness(
241                     rawState = WakefulnessState.ASLEEP,
242                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
243                     lastSleepReason = WakeSleepReason.OTHER,
244                 )
245                 runCurrent()
246 
247                 // WHEN a request comes to show the view
248                 controllerOverlay.show(udfpsController, overlayParams)
249                 runCurrent()
250 
251                 // THEN view isn't added yet
252                 verify(windowManager, never()).addView(any(), any())
253 
254                 // we hide to end the job that listens for the finishedGoingToSleep signal
255                 controllerOverlay.hide()
256             }
257         }
258 
259     @Test
260     fun neverRemoveViewThatHasNotBeenAdded() =
261         testScope.runTest {
262             withReasonSuspend(REASON_AUTH_KEYGUARD) {
263                 controllerOverlay.show(udfpsController, overlayParams)
264                 val view = controllerOverlay.getTouchOverlay()
265                 view?.let {
266                     // parent is null, signalling that the view was never added
267                     whenever(view.parent).thenReturn(null)
268                 }
269                 verify(windowManager, never()).removeView(eq(view))
270             }
271         }
272 
273     @Test
274     fun canNotHide() = withReason(REASON_AUTH_BP) { assertThat(controllerOverlay.hide()).isFalse() }
275 
276     @Test
277     fun cancels() =
278         withReason(REASON_AUTH_BP) {
279             controllerOverlay.cancel()
280             verify(controllerCallback).onUserCanceled()
281         }
282 
283     @Test
284     fun matchesRequestIds() =
285         withReason(REASON_AUTH_BP) {
286             assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
287             assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
288         }
289 
290     @Test
291     fun addViewPending_layoutIsNotUpdated() =
292         testScope.runTest {
293             withReasonSuspend(REASON_AUTH_KEYGUARD) {
294                 // GIVEN going to sleep
295                 keyguardTransitionRepository.sendTransitionSteps(
296                     from = KeyguardState.OFF,
297                     to = KeyguardState.GONE,
298                     testScope = this,
299                 )
300                 powerRepository.updateWakefulness(
301                     rawState = WakefulnessState.STARTING_TO_SLEEP,
302                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
303                     lastSleepReason = WakeSleepReason.OTHER,
304                 )
305                 runCurrent()
306 
307                 // WHEN a request comes to show the view
308                 controllerOverlay.show(udfpsController, overlayParams)
309                 runCurrent()
310 
311                 // THEN the view does not get added immediately
312                 verify(windowManager, never()).addView(any(), any())
313 
314                 // WHEN updateOverlayParams gets called when the view is pending to be added
315                 controllerOverlay.updateOverlayParams(overlayParams)
316 
317                 // THEN the view layout is never updated
318                 verify(windowManager, never()).updateViewLayout(any(), any())
319 
320                 // CLEANUP we hide to end the job that listens for the finishedGoingToSleep signal
321                 controllerOverlay.hide()
322             }
323         }
324 }
325