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 package com.android.systemui.biometrics
17 
18 import android.animation.ValueAnimator
19 import android.graphics.PointF
20 import android.graphics.RectF
21 import androidx.annotation.VisibleForTesting
22 import androidx.lifecycle.Lifecycle
23 import androidx.lifecycle.repeatOnLifecycle
24 import com.android.app.animation.Interpolators
25 import com.android.systemui.Dumpable
26 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
27 import com.android.systemui.dump.DumpManager
28 import com.android.systemui.lifecycle.repeatWhenAttached
29 import com.android.systemui.plugins.statusbar.StatusBarStateController
30 import com.android.systemui.shade.domain.interactor.ShadeInteractor
31 import com.android.systemui.statusbar.phone.SystemUIDialogManager
32 import com.android.systemui.util.ViewController
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.Job
35 import kotlinx.coroutines.launch
36 import java.io.PrintWriter
37 
38 /**
39  * Handles:
40  * 1. registering for listeners when its view is attached and unregistering on view detached
41  * 2. pausing UDFPS when FingerprintManager may still be running but we temporarily want to hide
42  * the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
43  * 3. sending events to its view including:
44  * - enabling and disabling of the UDFPS display mode
45  * - sensor position changes
46  * - doze time event
47  */
48 abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
49     view: T,
50     protected val statusBarStateController: StatusBarStateController,
51     protected val shadeInteractor: ShadeInteractor,
52     protected val dialogManager: SystemUIDialogManager,
53     private val dumpManager: DumpManager,
54     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
55 ) : ViewController<T>(view), Dumpable {
56 
57     protected abstract val tag: String
58 
59     private val view: T
60         get() = mView!!
61 
62     private var dialogAlphaAnimator: ValueAnimator? = null
63     private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
64 
65     /** If the notification shade is visible. */
66     var notificationShadeVisible: Boolean = false
67 
68     /**
69      * The amount of translation needed if the view currently requires the user to touch
70      * somewhere other than the exact center of the sensor. For example, this can happen
71      * during guided enrollment.
72      */
73     open val touchTranslation: PointF = PointF(0f, 0f)
74 
75     /**
76      * X-Padding to add to left and right of the sensor rectangle area to increase the size of our
77      * window to draw within.
78      */
79     open val paddingX: Int = 0
80 
81     /**
82      * Y-Padding to add to top and bottom of the sensor rectangle area to increase the size of our
83      * window to draw within.
84      */
85     open val paddingY: Int = 0
86 
87     open fun updateAlpha() {
88         view.updateAlpha()
89     }
90 
91     init {
92         view.repeatWhenAttached {
93             // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
94             // can make the view not visible; and we still want to listen for events
95             // that may make the view visible again.
96             repeatOnLifecycle(Lifecycle.State.CREATED) {
97                 listenForShadeExpansion(this)
98             }
99         }
100     }
101 
102     @VisibleForTesting
103     suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
104         return scope.launch {
105             shadeInteractor.anyExpansion.collect { expansion ->
106                 notificationShadeVisible = expansion > 0f
107                 view.onExpansionChanged(expansion)
108                 updatePauseAuth()
109             }
110         }
111     }
112 
113     fun runDialogAlphaAnimator() {
114         val hideAffordance = dialogManager.shouldHideAffordance()
115         dialogAlphaAnimator?.cancel()
116         dialogAlphaAnimator = ValueAnimator.ofFloat(
117                 view.calculateAlpha() / 255f,
118                 if (hideAffordance) 0f else 1f)
119                 .apply {
120             duration = if (hideAffordance) 83L else 200L
121             interpolator = if (hideAffordance) Interpolators.LINEAR else Interpolators.ALPHA_IN
122 
123             addUpdateListener { animatedValue ->
124                 view.setDialogSuggestedAlpha(animatedValue.animatedValue as Float)
125                 updateAlpha()
126                 updatePauseAuth()
127             }
128             start()
129         }
130     }
131 
132     override fun onViewAttached() {
133         dialogManager.registerListener(dialogListener)
134         dumpManager.registerDumpable(dumpTag, this)
135         udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
136     }
137 
138     override fun onViewDetached() {
139         dialogManager.unregisterListener(dialogListener)
140         dumpManager.unregisterDumpable(dumpTag)
141         udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
142     }
143 
144     /**
145      * in some cases, onViewAttached is called for the newly added view using an instance of
146      * this controller before onViewDetached is called on the previous view, so we must have a
147      * unique [dumpTag] per instance of this class.
148      */
149     private val dumpTag = "$tag ($this)"
150 
151     override fun dump(pw: PrintWriter, args: Array<String>) {
152         pw.println("mNotificationShadeVisible=$notificationShadeVisible")
153         pw.println("shouldPauseAuth()=" + shouldPauseAuth())
154         pw.println("isPauseAuth=" + view.isPauseAuth)
155         pw.println("dialogSuggestedAlpha=" + view.dialogSuggestedAlpha)
156     }
157 
158     /**
159      * Returns true if the fingerprint manager is running, but we want to temporarily pause
160      * authentication.
161      */
162     open fun shouldPauseAuth(): Boolean {
163         return notificationShadeVisible || dialogManager.shouldHideAffordance()
164     }
165 
166     /**
167      * Send pause auth update to our view.
168      */
169     fun updatePauseAuth() {
170         if (view.setPauseAuth(shouldPauseAuth())) {
171             view.postInvalidate()
172             udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
173         }
174     }
175 
176     /**
177      * Send sensor position change to our view. This rect contains paddingX and paddingY.
178      */
179     fun onSensorRectUpdated(sensorRect: RectF) {
180         view.onSensorRectUpdated(sensorRect)
181     }
182 
183     /**
184      * Send dozeTimeTick to view in case it wants to handle its burn-in offset.
185      */
186     fun dozeTimeTick() {
187         if (view.dozeTimeTick()) {
188             view.postInvalidate()
189         }
190     }
191 
192     /**
193      * The display began transitioning into the UDFPS mode and the fingerprint manager started
194      * authenticating.
195      */
196     fun onDisplayConfiguring() {
197         view.onDisplayConfiguring()
198         view.postInvalidate()
199     }
200 
201     /**
202      * The display transitioned away from the UDFPS mode and the fingerprint manager stopped
203      * authenticating.
204      */
205     fun onDisplayUnconfigured() {
206         view.onDisplayUnconfigured()
207         view.postInvalidate()
208     }
209 
210     /**
211      * Whether to listen for touches outside of the view.
212      */
213     open fun listenForTouchesOutsideView(): Boolean = false
214 
215     /**
216      * Called when a view should announce an accessibility event.
217      */
218     open fun doAnnounceForAccessibility(str: String) {}
219 }
220