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