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