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.animation.ValueAnimator 20 import android.content.res.Configuration 21 import android.util.MathUtils 22 import android.view.MotionEvent 23 import android.view.View 24 import androidx.annotation.VisibleForTesting 25 import androidx.lifecycle.Lifecycle 26 import androidx.lifecycle.repeatOnLifecycle 27 import com.android.app.animation.Interpolators 28 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress 29 import com.android.keyguard.KeyguardUpdateMonitor 30 import com.android.systemui.R 31 import com.android.systemui.animation.ActivityLaunchAnimator 32 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 33 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor 34 import com.android.systemui.dump.DumpManager 35 import com.android.systemui.flags.FeatureFlags 36 import com.android.systemui.flags.Flags 37 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter 38 import com.android.systemui.lifecycle.repeatWhenAttached 39 import com.android.systemui.plugins.statusbar.StatusBarStateController 40 import com.android.systemui.shade.ShadeExpansionListener 41 import com.android.systemui.shade.ShadeExpansionStateManager 42 import com.android.systemui.statusbar.LockscreenShadeTransitionController 43 import com.android.systemui.statusbar.StatusBarState 44 import com.android.systemui.statusbar.notification.stack.StackStateAnimator 45 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 46 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback 47 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI 48 import com.android.systemui.statusbar.phone.SystemUIDialogManager 49 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController 50 import com.android.systemui.statusbar.policy.ConfigurationController 51 import com.android.systemui.statusbar.policy.KeyguardStateController 52 import java.io.PrintWriter 53 import kotlinx.coroutines.CoroutineScope 54 import kotlinx.coroutines.Job 55 import kotlinx.coroutines.launch 56 57 /** Class that coordinates non-HBM animations during keyguard authentication. */ 58 open class UdfpsKeyguardViewControllerLegacy 59 constructor( 60 private val view: UdfpsKeyguardViewLegacy, 61 statusBarStateController: StatusBarStateController, 62 shadeExpansionStateManager: ShadeExpansionStateManager, 63 private val keyguardViewManager: StatusBarKeyguardViewManager, 64 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 65 dumpManager: DumpManager, 66 private val lockScreenShadeTransitionController: LockscreenShadeTransitionController, 67 private val configurationController: ConfigurationController, 68 private val keyguardStateController: KeyguardStateController, 69 private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, 70 systemUIDialogManager: SystemUIDialogManager, 71 private val udfpsController: UdfpsController, 72 private val activityLaunchAnimator: ActivityLaunchAnimator, 73 featureFlags: FeatureFlags, 74 private val primaryBouncerInteractor: PrimaryBouncerInteractor, 75 private val alternateBouncerInteractor: AlternateBouncerInteractor, 76 private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, 77 ) : 78 UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>( 79 view, 80 statusBarStateController, 81 shadeExpansionStateManager, 82 systemUIDialogManager, 83 dumpManager, 84 ), 85 UdfpsKeyguardViewControllerAdapter { 86 private val uniqueIdentifier = this.toString() 87 private val useExpandedOverlay: Boolean = 88 featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) 89 private var showingUdfpsBouncer = false 90 private var udfpsRequested = false 91 private var qsExpansion = 0f 92 private var faceDetectRunning = false 93 private var statusBarState = 0 94 private var transitionToFullShadeProgress = 0f 95 private var lastDozeAmount = 0f 96 private var panelExpansionFraction = 0f 97 private var launchTransitionFadingAway = false 98 private var isLaunchingActivity = false 99 private var activityLaunchProgress = 0f 100 private val unlockedScreenOffDozeAnimator = 101 ValueAnimator.ofFloat(0f, 1f).apply { 102 duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong() 103 interpolator = Interpolators.ALPHA_IN 104 addUpdateListener { animation -> 105 view.onDozeAmountChanged( 106 animation.animatedFraction, 107 animation.animatedValue as Float, 108 UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF 109 ) 110 } 111 } 112 private var inputBouncerExpansion = 0f 113 114 private val stateListener: StatusBarStateController.StateListener = 115 object : StatusBarStateController.StateListener { 116 override fun onDozeAmountChanged(linear: Float, eased: Float) { 117 if (lastDozeAmount < linear) { 118 showUdfpsBouncer(false) 119 } 120 unlockedScreenOffDozeAnimator.cancel() 121 val animatingFromUnlockedScreenOff = 122 unlockedScreenOffAnimationController.isAnimationPlaying() 123 if (animatingFromUnlockedScreenOff && linear != 0f) { 124 // we manually animate the fade in of the UDFPS icon since the unlocked 125 // screen off animation prevents the doze amounts to be incrementally eased in 126 unlockedScreenOffDozeAnimator.start() 127 } else { 128 view.onDozeAmountChanged( 129 linear, 130 eased, 131 UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN 132 ) 133 } 134 lastDozeAmount = linear 135 updatePauseAuth() 136 } 137 138 override fun onStateChanged(statusBarState: Int) { 139 this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState 140 updateAlpha() 141 updatePauseAuth() 142 } 143 } 144 145 private val configurationListener: ConfigurationController.ConfigurationListener = 146 object : ConfigurationController.ConfigurationListener { 147 override fun onUiModeChanged() { 148 view.updateColor() 149 } 150 151 override fun onThemeChanged() { 152 view.updateColor() 153 } 154 155 override fun onConfigChanged(newConfig: Configuration) { 156 updateScaleFactor() 157 view.updatePadding() 158 view.updateColor() 159 } 160 } 161 162 private val shadeExpansionListener = ShadeExpansionListener { (fraction) -> 163 panelExpansionFraction = 164 if (keyguardViewManager.isPrimaryBouncerInTransit) { 165 aboutToShowBouncerProgress(fraction) 166 } else { 167 fraction 168 } 169 updateAlpha() 170 updatePauseAuth() 171 } 172 173 private val keyguardStateControllerCallback: KeyguardStateController.Callback = 174 object : KeyguardStateController.Callback { 175 override fun onUnlockedChanged() { 176 updatePauseAuth() 177 } 178 override fun onLaunchTransitionFadingAwayChanged() { 179 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway 180 updatePauseAuth() 181 } 182 } 183 184 private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener = 185 object : ActivityLaunchAnimator.Listener { 186 override fun onLaunchAnimationStart() { 187 isLaunchingActivity = true 188 activityLaunchProgress = 0f 189 updateAlpha() 190 } 191 192 override fun onLaunchAnimationEnd() { 193 isLaunchingActivity = false 194 updateAlpha() 195 } 196 197 override fun onLaunchAnimationProgress(linearProgress: Float) { 198 activityLaunchProgress = linearProgress 199 updateAlpha() 200 } 201 } 202 203 private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback = 204 object : KeyguardViewManagerCallback { 205 override fun onQSExpansionChanged(qsExpansion: Float) { 206 this@UdfpsKeyguardViewControllerLegacy.qsExpansion = qsExpansion 207 updateAlpha() 208 updatePauseAuth() 209 } 210 211 /** 212 * Forward touches to the UdfpsController. This allows the touch to start from outside 213 * the sensor area and then slide their finger into the sensor area. 214 */ 215 override fun onTouch(event: MotionEvent) { 216 // Don't forward touches if the shade has already started expanding. 217 if (transitionToFullShadeProgress != 0f) { 218 return 219 } 220 221 // Forwarding touches not needed with expanded overlay 222 if (useExpandedOverlay) { 223 return 224 } else { 225 udfpsController.onTouch(event) 226 } 227 } 228 } 229 230 private val occludingAppBiometricUI: OccludingAppBiometricUI = 231 object : OccludingAppBiometricUI { 232 override fun requestUdfps(request: Boolean, color: Int) { 233 udfpsRequested = request 234 view.requestUdfps(request, color) 235 updateAlpha() 236 updatePauseAuth() 237 } 238 239 override fun dump(pw: PrintWriter) { 240 pw.println(tag) 241 } 242 } 243 244 override val tag: String 245 get() = TAG 246 247 override fun onInit() { 248 super.onInit() 249 keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) 250 } 251 252 init { 253 view.repeatWhenAttached { 254 // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion 255 // can make the view not visible; and we still want to listen for events 256 // that may make the view visible again. 257 repeatOnLifecycle(Lifecycle.State.CREATED) { 258 listenForBouncerExpansion(this) 259 listenForAlternateBouncerVisibility(this) 260 } 261 } 262 } 263 264 @VisibleForTesting 265 suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { 266 return scope.launch { 267 primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> 268 inputBouncerExpansion = bouncerExpansion 269 updateAlpha() 270 updatePauseAuth() 271 } 272 } 273 } 274 275 @VisibleForTesting 276 suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { 277 return scope.launch { 278 alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> 279 showUdfpsBouncer(isVisible) 280 } 281 } 282 } 283 284 public override fun onViewAttached() { 285 super.onViewAttached() 286 alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) 287 val dozeAmount = statusBarStateController.dozeAmount 288 lastDozeAmount = dozeAmount 289 stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) 290 statusBarStateController.addCallback(stateListener) 291 udfpsRequested = false 292 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway 293 keyguardStateController.addCallback(keyguardStateControllerCallback) 294 statusBarState = statusBarStateController.state 295 qsExpansion = keyguardViewManager.qsExpansion 296 keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback) 297 configurationController.addCallback(configurationListener) 298 val currentState = shadeExpansionStateManager.addExpansionListener(shadeExpansionListener) 299 shadeExpansionListener.onPanelExpansionChanged(currentState) 300 updateScaleFactor() 301 view.updatePadding() 302 updateAlpha() 303 updatePauseAuth() 304 keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) 305 lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this 306 activityLaunchAnimator.addListener(activityLaunchAnimatorListener) 307 view.mUseExpandedOverlay = useExpandedOverlay 308 view.startIconAsyncInflate { 309 val animationViewInternal: View = 310 view.requireViewById(R.id.udfps_animation_view_internal) 311 animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate 312 } 313 } 314 315 override fun onViewDetached() { 316 super.onViewDetached() 317 alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) 318 faceDetectRunning = false 319 keyguardStateController.removeCallback(keyguardStateControllerCallback) 320 statusBarStateController.removeCallback(stateListener) 321 keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI) 322 keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) 323 configurationController.removeCallback(configurationListener) 324 shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener) 325 if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) { 326 lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null 327 } 328 activityLaunchAnimator.removeListener(activityLaunchAnimatorListener) 329 keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) 330 } 331 332 override fun dump(pw: PrintWriter, args: Array<String>) { 333 super.dump(pw, args) 334 pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") 335 pw.println( 336 "altBouncerInteractor#isAlternateBouncerVisible=" + 337 "${alternateBouncerInteractor.isVisibleState()}" 338 ) 339 pw.println( 340 "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" + 341 "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}" 342 ) 343 pw.println("faceDetectRunning=$faceDetectRunning") 344 pw.println("statusBarState=" + StatusBarState.toString(statusBarState)) 345 pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress") 346 pw.println("qsExpansion=$qsExpansion") 347 pw.println("panelExpansionFraction=$panelExpansionFraction") 348 pw.println("unpausedAlpha=" + view.unpausedAlpha) 349 pw.println("udfpsRequestedByApp=$udfpsRequested") 350 pw.println("launchTransitionFadingAway=$launchTransitionFadingAway") 351 pw.println("lastDozeAmount=$lastDozeAmount") 352 pw.println("inputBouncerExpansion=$inputBouncerExpansion") 353 view.dump(pw) 354 } 355 356 /** 357 * Overrides non-bouncer show logic in shouldPauseAuth to still show icon. 358 * 359 * @return whether the udfpsBouncer has been newly shown or hidden 360 */ 361 private fun showUdfpsBouncer(show: Boolean): Boolean { 362 if (showingUdfpsBouncer == show) { 363 return false 364 } 365 val udfpsAffordanceWasNotShowing = shouldPauseAuth() 366 showingUdfpsBouncer = show 367 if (showingUdfpsBouncer) { 368 if (udfpsAffordanceWasNotShowing) { 369 view.animateInUdfpsBouncer(null) 370 } 371 if (keyguardStateController.isOccluded) { 372 keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true) 373 } 374 view.announceForAccessibility( 375 view.context.getString(R.string.accessibility_fingerprint_bouncer) 376 ) 377 } else { 378 keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) 379 } 380 updateAlpha() 381 updatePauseAuth() 382 return true 383 } 384 385 /** 386 * Returns true if the fingerprint manager is running but we want to temporarily pause 387 * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so 388 * this can be overridden with the showBouncer method. 389 */ 390 override fun shouldPauseAuth(): Boolean { 391 if (showingUdfpsBouncer) { 392 return false 393 } 394 if ( 395 udfpsRequested && 396 !notificationShadeVisible && 397 !isInputBouncerFullyVisible() && 398 keyguardStateController.isShowing 399 ) { 400 return false 401 } 402 if (launchTransitionFadingAway) { 403 return true 404 } 405 406 // Only pause auth if we're not on the keyguard AND we're not transitioning to doze. 407 // For the UnlockedScreenOffAnimation, the statusBarState is 408 // delayed. However, we still animate in the UDFPS affordance with the 409 // unlockedScreenOffDozeAnimator. 410 if ( 411 statusBarState != StatusBarState.KEYGUARD && 412 !unlockedScreenOffAnimationController.isAnimationPlaying() 413 ) { 414 return true 415 } 416 if (isBouncerExpansionGreaterThan(.5f)) { 417 return true 418 } 419 if ( 420 keyguardUpdateMonitor.getUserUnlockedWithBiometric( 421 KeyguardUpdateMonitor.getCurrentUser() 422 ) 423 ) { 424 // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid 425 // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running. 426 return true 427 } 428 return view.unpausedAlpha < 255 * .1 429 } 430 431 fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean { 432 return inputBouncerExpansion >= bouncerExpansionThreshold 433 } 434 435 fun isInputBouncerFullyVisible(): Boolean { 436 return inputBouncerExpansion == 1f 437 } 438 439 override fun listenForTouchesOutsideView(): Boolean { 440 return true 441 } 442 443 /** 444 * Set the progress we're currently transitioning to the full shade. 0.0f means we're not 445 * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down 446 * to expand the notification shade from the empty space in the middle of the lock screen. 447 */ 448 fun setTransitionToFullShadeProgress(progress: Float) { 449 transitionToFullShadeProgress = progress 450 updateAlpha() 451 } 452 453 /** 454 * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is 455 * based on the doze amount. 456 */ 457 override fun updateAlpha() { 458 // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested, 459 // then the keyguard is occluded by some application - so instead use the input bouncer 460 // hidden amount to determine the fade. 461 val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction 462 var alpha: Int = 463 if (showingUdfpsBouncer) 255 464 else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt() 465 if (!showingUdfpsBouncer) { 466 // swipe from top of the lockscreen to expand full QS: 467 alpha = 468 (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion))) 469 .toInt() 470 471 // swipe from the middle (empty space) of lockscreen to expand the notification shade: 472 alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt() 473 474 // Fade out the icon if we are animating an activity launch over the lockscreen and the 475 // activity didn't request the UDFPS. 476 if (isLaunchingActivity && !udfpsRequested) { 477 val udfpsActivityLaunchAlphaMultiplier = 478 1f - 479 (activityLaunchProgress * 480 (ActivityLaunchAnimator.TIMINGS.totalDuration / 83)) 481 .coerceIn(0f, 1f) 482 alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt() 483 } 484 485 // Fade out alpha when a dialog is shown 486 // Fade in alpha when a dialog is hidden 487 alpha = (alpha * view.dialogSuggestedAlpha).toInt() 488 } 489 view.unpausedAlpha = alpha 490 } 491 492 private fun getInputBouncerHiddenAmt(): Float { 493 return 1f - inputBouncerExpansion 494 } 495 496 /** Update the scale factor based on the device's resolution. */ 497 private fun updateScaleFactor() { 498 udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } 499 } 500 companion object { 501 const val TAG = "UdfpsKeyguardViewController" 502 } 503 } 504