blob: 66fb8ca2832d2c4afa788b1708f4376279e37896 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.animation.ValueAnimator
import android.graphics.PointF
import android.graphics.RectF
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.ViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.PrintWriter
/**
* Handles:
* 1. registering for listeners when its view is attached and unregistering on view detached
* 2. pausing UDFPS when FingerprintManager may still be running but we temporarily want to hide
* the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
* 3. sending events to its view including:
* - enabling and disabling of the UDFPS display mode
* - sensor position changes
* - doze time event
*/
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
protected abstract val tag: String
private val view: T
get() = mView!!
private var dialogAlphaAnimator: ValueAnimator? = null
private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
/** If the notification shade is visible. */
var notificationShadeVisible: Boolean = false
/**
* The amount of translation needed if the view currently requires the user to touch
* somewhere other than the exact center of the sensor. For example, this can happen
* during guided enrollment.
*/
open val touchTranslation: PointF = PointF(0f, 0f)
/**
* X-Padding to add to left and right of the sensor rectangle area to increase the size of our
* window to draw within.
*/
open val paddingX: Int = 0
/**
* Y-Padding to add to top and bottom of the sensor rectangle area to increase the size of our
* window to draw within.
*/
open val paddingY: Int = 0
open fun updateAlpha() {
view.updateAlpha()
}
init {
view.repeatWhenAttached {
// repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
// can make the view not visible; and we still want to listen for events
// that may make the view visible again.
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForBouncerExpansion(this)
}
}
}
@VisibleForTesting
open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
if (statusBarStateController.state != SHADE) {
notificationShadeVisible = expansion > 0f
view.onExpansionChanged(expansion)
updatePauseAuth()
}
}
}
}
fun runDialogAlphaAnimator() {
val hideAffordance = dialogManager.shouldHideAffordance()
dialogAlphaAnimator?.cancel()
dialogAlphaAnimator = ValueAnimator.ofFloat(
view.calculateAlpha() / 255f,
if (hideAffordance) 0f else 1f)
.apply {
duration = if (hideAffordance) 83L else 200L
interpolator = if (hideAffordance) Interpolators.LINEAR else Interpolators.ALPHA_IN
addUpdateListener { animatedValue ->
view.setDialogSuggestedAlpha(animatedValue.animatedValue as Float)
updateAlpha()
updatePauseAuth()
}
start()
}
}
override fun onViewAttached() {
dialogManager.registerListener(dialogListener)
dumpManager.registerDumpable(dumpTag, this)
}
override fun onViewDetached() {
dialogManager.unregisterListener(dialogListener)
dumpManager.unregisterDumpable(dumpTag)
}
/**
* in some cases, onViewAttached is called for the newly added view using an instance of
* this controller before onViewDetached is called on the previous view, so we must have a
* unique [dumpTag] per instance of this class.
*/
private val dumpTag = "$tag ($this)"
override fun dump(pw: PrintWriter, args: Array<String>) {
pw.println("mNotificationShadeVisible=$notificationShadeVisible")
pw.println("shouldPauseAuth()=" + shouldPauseAuth())
pw.println("isPauseAuth=" + view.isPauseAuth)
pw.println("dialogSuggestedAlpha=" + view.dialogSuggestedAlpha)
}
/**
* Returns true if the fingerprint manager is running, but we want to temporarily pause
* authentication.
*/
open fun shouldPauseAuth(): Boolean {
return notificationShadeVisible || dialogManager.shouldHideAffordance()
}
/**
* Send pause auth update to our view.
*/
fun updatePauseAuth() {
if (view.setPauseAuth(shouldPauseAuth())) {
view.postInvalidate()
}
}
/**
* Send sensor position change to our view. This rect contains paddingX and paddingY.
*/
fun onSensorRectUpdated(sensorRect: RectF) {
view.onSensorRectUpdated(sensorRect)
}
/**
* Send dozeTimeTick to view in case it wants to handle its burn-in offset.
*/
fun dozeTimeTick() {
if (view.dozeTimeTick()) {
view.postInvalidate()
}
}
/**
* The display began transitioning into the UDFPS mode and the fingerprint manager started
* authenticating.
*/
fun onDisplayConfiguring() {
view.onDisplayConfiguring()
view.postInvalidate()
}
/**
* The display transitioned away from the UDFPS mode and the fingerprint manager stopped
* authenticating.
*/
fun onDisplayUnconfigured() {
view.onDisplayUnconfigured()
view.postInvalidate()
}
/**
* Whether to listen for touches outside of the view.
*/
open fun listenForTouchesOutsideView(): Boolean = false
/**
* Called when a view should announce an accessibility event.
*/
open fun doAnnounceForAccessibility(str: String) {}
}