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