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