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