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