1 /*
2  * 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.graphics.Rect
20 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
21 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
22 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
23 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
24 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
25 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
26 import android.hardware.fingerprint.FingerprintManager
27 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
28 import android.testing.TestableLooper.RunWithLooper
29 import android.view.LayoutInflater
30 import android.view.MotionEvent
31 import android.view.Surface
32 import android.view.Surface.ROTATION_0
33 import android.view.Surface.Rotation
34 import android.view.View
35 import android.view.WindowManager
36 import android.view.accessibility.AccessibilityManager
37 import androidx.test.ext.junit.runners.AndroidJUnit4
38 import androidx.test.filters.SmallTest
39 import com.android.keyguard.KeyguardUpdateMonitor
40 import com.android.settingslib.udfps.UdfpsOverlayParams
41 import com.android.settingslib.udfps.UdfpsUtils
42 import com.android.systemui.R
43 import com.android.systemui.RoboPilotTest
44 import com.android.systemui.SysuiTestCase
45 import com.android.systemui.animation.ActivityLaunchAnimator
46 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
47 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
48 import com.android.systemui.dump.DumpManager
49 import com.android.systemui.flags.FeatureFlags
50 import com.android.systemui.flags.Flags
51 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
52 import com.android.systemui.plugins.statusbar.StatusBarStateController
53 import com.android.systemui.shade.ShadeExpansionStateManager
54 import com.android.systemui.statusbar.LockscreenShadeTransitionController
55 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
56 import com.android.systemui.statusbar.phone.SystemUIDialogManager
57 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
58 import com.android.systemui.statusbar.policy.ConfigurationController
59 import com.android.systemui.statusbar.policy.KeyguardStateController
60 import com.android.systemui.util.settings.SecureSettings
61 import com.google.common.truth.Truth.assertThat
62 import kotlinx.coroutines.ExperimentalCoroutinesApi
63 import org.junit.Before
64 import org.junit.Rule
65 import org.junit.Test
66 import org.junit.runner.RunWith
67 import org.mockito.ArgumentCaptor
68 import org.mockito.ArgumentMatchers.any
69 import org.mockito.ArgumentMatchers.eq
70 import org.mockito.Captor
71 import org.mockito.Mock
72 import org.mockito.Mockito.mock
73 import org.mockito.Mockito.verify
74 import org.mockito.junit.MockitoJUnit
75 import javax.inject.Provider
76 import org.mockito.Mockito.`when` as whenever
77 
78 private const val REQUEST_ID = 2L
79 
80 // Dimensions for the current display resolution.
81 private const val DISPLAY_WIDTH = 1080
82 private const val DISPLAY_HEIGHT = 1920
83 private const val SENSOR_WIDTH = 30
84 private const val SENSOR_HEIGHT = 60
85 
86 @ExperimentalCoroutinesApi
87 @SmallTest
88 @RoboPilotTest
89 @RunWith(AndroidJUnit4::class)
90 @RunWithLooper(setAsMainLooper = true)
91 class UdfpsControllerOverlayTest : SysuiTestCase() {
92 
93     @JvmField @Rule var rule = MockitoJUnit.rule()
94 
95     @Mock private lateinit var fingerprintManager: FingerprintManager
96     @Mock private lateinit var inflater: LayoutInflater
97     @Mock private lateinit var windowManager: WindowManager
98     @Mock private lateinit var accessibilityManager: AccessibilityManager
99     @Mock private lateinit var statusBarStateController: StatusBarStateController
100     @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
101     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
102     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
103     @Mock private lateinit var dialogManager: SystemUIDialogManager
104     @Mock private lateinit var dumpManager: DumpManager
105     @Mock private lateinit var transitionController: LockscreenShadeTransitionController
106     @Mock private lateinit var configurationController: ConfigurationController
107     @Mock private lateinit var keyguardStateController: KeyguardStateController
108     @Mock private lateinit var unlockedScreenOffAnimationController:
109             UnlockedScreenOffAnimationController
110     @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
111     @Mock private lateinit var secureSettings: SecureSettings
112     @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
113     @Mock private lateinit var udfpsController: UdfpsController
114     @Mock private lateinit var udfpsView: UdfpsView
115     @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy
116     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
117     @Mock private lateinit var featureFlags: FeatureFlags
118     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
119     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
120     @Mock private lateinit var udfpsUtils: UdfpsUtils
121     @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
122             UdfpsKeyguardAccessibilityDelegate
123     @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
124     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
125 
126     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
127     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
128     private lateinit var controllerOverlay: UdfpsControllerOverlay
129 
130     @Before
131     fun setup() {
132         whenever(inflater.inflate(R.layout.udfps_view, null, false))
133             .thenReturn(udfpsView)
134         whenever(inflater.inflate(R.layout.udfps_bp_view, null))
135             .thenReturn(mock(UdfpsBpView::class.java))
136         whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
137             .thenReturn(mUdfpsKeyguardViewLegacy)
138         whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null))
139             .thenReturn(mock(UdfpsFpmEmptyView::class.java))
140     }
141 
142     private fun withReason(
143         @ShowReason reason: Int,
144         isDebuggable: Boolean = false,
145         block: () -> Unit
146     ) {
147         controllerOverlay = UdfpsControllerOverlay(
148             context, fingerprintManager, inflater, windowManager, accessibilityManager,
149             statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
150             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
151             configurationController, keyguardStateController, unlockedScreenOffAnimationController,
152             udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
153             controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
154             primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
155             udfpsKeyguardAccessibilityDelegate,
156             udfpsKeyguardViewModels,
157         )
158         block()
159     }
160 
161     @Test
162     fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() }
163 
164     @Test
165     fun showUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) {
166         showUdfpsOverlay()
167         verify(mUdfpsKeyguardViewLegacy).updateSensorLocation(eq(overlayParams.sensorBounds))
168     }
169 
170     @Test
171     fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
172 
173     private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
174         // Sensor that's in the top left corner of the display in natural orientation.
175         val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
176         val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
177         overlayParams = UdfpsOverlayParams(
178             sensorBounds,
179             overlayBounds,
180             DISPLAY_WIDTH,
181             DISPLAY_HEIGHT,
182             scaleFactor = 1f,
183             rotation
184         )
185         block()
186     }
187 
188     @Test
189     fun showUdfpsOverlay_withRotation0() = withRotation(Surface.ROTATION_0) {
190         withReason(REASON_AUTH_BP) {
191             controllerOverlay.show(udfpsController, overlayParams)
192             verify(windowManager).addView(
193                 eq(controllerOverlay.overlayView),
194                 layoutParamsCaptor.capture()
195             )
196 
197             // ROTATION_0 is the native orientation. Sensor should stay in the top left corner.
198             val lp = layoutParamsCaptor.value
199             assertThat(lp.x).isEqualTo(0)
200             assertThat(lp.y).isEqualTo(0)
201             assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
202             assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
203         }
204     }
205 
206     @Test
207     fun showUdfpsOverlay_withRotation180() = withRotation(Surface.ROTATION_180) {
208         withReason(REASON_AUTH_BP) {
209             controllerOverlay.show(udfpsController, overlayParams)
210             verify(windowManager).addView(
211                 eq(controllerOverlay.overlayView),
212                 layoutParamsCaptor.capture()
213             )
214 
215             // ROTATION_180 is not supported. Sensor should stay in the top left corner.
216             val lp = layoutParamsCaptor.value
217             assertThat(lp.x).isEqualTo(0)
218             assertThat(lp.y).isEqualTo(0)
219             assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
220             assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
221         }
222     }
223 
224     @Test
225     fun showUdfpsOverlay_withRotation90() = withRotation(Surface.ROTATION_90) {
226         withReason(REASON_AUTH_BP) {
227             controllerOverlay.show(udfpsController, overlayParams)
228             verify(windowManager).addView(
229                 eq(controllerOverlay.overlayView),
230                 layoutParamsCaptor.capture()
231             )
232 
233             // Sensor should be in the bottom left corner in ROTATION_90.
234             val lp = layoutParamsCaptor.value
235             assertThat(lp.x).isEqualTo(0)
236             assertThat(lp.y).isEqualTo(DISPLAY_WIDTH - SENSOR_WIDTH)
237             assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
238             assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
239         }
240     }
241 
242     @Test
243     fun showUdfpsOverlay_withRotation270() = withRotation(Surface.ROTATION_270) {
244         withReason(REASON_AUTH_BP) {
245             controllerOverlay.show(udfpsController, overlayParams)
246             verify(windowManager).addView(
247                 eq(controllerOverlay.overlayView),
248                 layoutParamsCaptor.capture()
249             )
250 
251             // Sensor should be in the top right corner in ROTATION_270.
252             val lp = layoutParamsCaptor.value
253             assertThat(lp.x).isEqualTo(DISPLAY_HEIGHT - SENSOR_HEIGHT)
254             assertThat(lp.y).isEqualTo(0)
255             assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
256             assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
257         }
258     }
259 
260     private fun showUdfpsOverlay() {
261         val didShow = controllerOverlay.show(udfpsController, overlayParams)
262 
263         verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
264         verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode))
265         verify(udfpsView).animationViewController = any()
266         verify(udfpsView).addView(any())
267 
268         assertThat(didShow).isTrue()
269         assertThat(controllerOverlay.isShowing).isTrue()
270         assertThat(controllerOverlay.isHiding).isFalse()
271         assertThat(controllerOverlay.overlayView).isNotNull()
272     }
273 
274     @Test
275     fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() }
276 
277     @Test
278     fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() }
279 
280     @Test
281     fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() }
282 
283     @Test
284     fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() }
285 
286     private fun hideUdfpsOverlay() {
287         val didShow = controllerOverlay.show(udfpsController, overlayParams)
288         val view = controllerOverlay.overlayView
289         val didHide = controllerOverlay.hide()
290 
291         verify(windowManager).removeView(eq(view))
292 
293         assertThat(didShow).isTrue()
294         assertThat(didHide).isTrue()
295         assertThat(controllerOverlay.overlayView).isNull()
296         assertThat(controllerOverlay.animationViewController).isNull()
297         assertThat(controllerOverlay.isShowing).isFalse()
298         assertThat(controllerOverlay.isHiding).isTrue()
299     }
300 
301     @Test
302     fun canNotHide() = withReason(REASON_AUTH_BP) {
303         assertThat(controllerOverlay.hide()).isFalse()
304     }
305 
306     @Test
307     fun canNotReshow() = withReason(REASON_AUTH_BP) {
308         assertThat(controllerOverlay.show(udfpsController, overlayParams)).isTrue()
309         assertThat(controllerOverlay.show(udfpsController, overlayParams)).isFalse()
310     }
311 
312     @Test
313     fun cancels() = withReason(REASON_AUTH_BP) {
314         controllerOverlay.cancel()
315         verify(controllerCallback).onUserCanceled()
316     }
317 
318     @Test
319     fun unconfigureDisplayOnHide() = withReason(REASON_AUTH_BP) {
320         whenever(udfpsView.isDisplayConfigured).thenReturn(true)
321 
322         controllerOverlay.show(udfpsController, overlayParams)
323         controllerOverlay.hide()
324         verify(udfpsView).unconfigureDisplay()
325     }
326 
327     @Test
328     fun matchesRequestIds() = withReason(REASON_AUTH_BP) {
329         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
330         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
331     }
332 
333     @Test
334     fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
335         withReason(REASON_ENROLL_ENROLLING) {
336             // When a11y enabled during enrollment
337             whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
338             whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
339 
340             controllerOverlay.show(udfpsController, overlayParams)
341             verify(windowManager).addView(
342                 eq(controllerOverlay.overlayView),
343                 layoutParamsCaptor.capture()
344             )
345 
346             // Layout params should use sensor bounds
347             val lp = layoutParamsCaptor.value
348             assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width())
349             assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
350         }
351     }
352 
353     @Test
354     fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) {
355         withReason(REASON_AUTH_KEYGUARD) {
356             whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
357 
358             controllerOverlay.show(udfpsController, overlayParams)
359             verify(windowManager).addView(
360                     eq(controllerOverlay.overlayView),
361                     layoutParamsCaptor.capture()
362             )
363 
364             // Layout params should use natural display width and height
365             val lp = layoutParamsCaptor.value
366             assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth)
367             assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight)
368         }
369     }
370 }
371