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