1 /* 2 * Copyright (C) 2022 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 17 package com.android.systemui 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.AnimatorSet 22 import android.animation.TimeInterpolator 23 import android.animation.ValueAnimator 24 import android.content.Context 25 import android.graphics.Canvas 26 import android.graphics.Color 27 import android.graphics.Matrix 28 import android.graphics.Paint 29 import android.graphics.Path 30 import android.graphics.RectF 31 import android.view.View 32 import androidx.core.graphics.ColorUtils 33 import com.android.app.animation.Interpolators 34 import com.android.keyguard.KeyguardUpdateMonitor 35 import com.android.settingslib.Utils 36 import com.android.systemui.biometrics.AuthController 37 import com.android.systemui.log.ScreenDecorationsLogger 38 import com.android.systemui.plugins.statusbar.StatusBarStateController 39 import com.android.systemui.util.asIndenting 40 import java.io.PrintWriter 41 import java.util.concurrent.Executor 42 43 /** 44 * When the face is enrolled, we use this view to show the face scanning animation and the camera 45 * protection on the keyguard. 46 */ 47 class FaceScanningOverlay( 48 context: Context, 49 pos: Int, 50 val statusBarStateController: StatusBarStateController, 51 val keyguardUpdateMonitor: KeyguardUpdateMonitor, 52 val mainExecutor: Executor, 53 val logger: ScreenDecorationsLogger, 54 val authController: AuthController, 55 ) : ScreenDecorations.DisplayCutoutView(context, pos) { 56 private var showScanningAnim = false 57 private val rimPaint = Paint() 58 private var rimProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE 59 private var rimAnimator: AnimatorSet? = null 60 private val rimRect = RectF() 61 private var cameraProtectionColor = Color.BLACK 62 63 var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context, 64 com.android.internal.R.attr.materialColorPrimaryFixed) 65 private var cameraProtectionAnimator: ValueAnimator? = null 66 var hideOverlayRunnable: Runnable? = null 67 68 init { 69 visibility = View.INVISIBLE // only show this view when face scanning is happening 70 } 71 setColornull72 override fun setColor(color: Int) { 73 cameraProtectionColor = color 74 invalidate() 75 } 76 drawCutoutProtectionnull77 override fun drawCutoutProtection(canvas: Canvas) { 78 if (protectionRect.isEmpty) { 79 return 80 } 81 if (rimProgress > HIDDEN_RIM_SCALE) { 82 drawFaceScanningRim(canvas) 83 } 84 if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE) { 85 drawCameraProtection(canvas) 86 } 87 } 88 enableShowProtectionnull89 override fun enableShowProtection(isCameraActive: Boolean) { 90 val scanningAnimationRequiredWhenCameraActive = 91 keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing 92 val faceAuthSucceeded = keyguardUpdateMonitor.isFaceAuthenticated 93 val showScanningAnimationNow = scanningAnimationRequiredWhenCameraActive && isCameraActive 94 if (showScanningAnimationNow == showScanningAnim) { 95 return 96 } 97 logger.cameraProtectionShownOrHidden( 98 showScanningAnimationNow, 99 keyguardUpdateMonitor.isFaceDetectionRunning, 100 authController.isShowing, 101 faceAuthSucceeded, 102 isCameraActive, 103 showScanningAnim) 104 showScanningAnim = showScanningAnimationNow 105 updateProtectionBoundingPath() 106 // Delay the relayout until the end of the animation when hiding, 107 // otherwise we'd clip it. 108 if (showScanningAnim) { 109 visibility = View.VISIBLE 110 requestLayout() 111 } 112 113 cameraProtectionAnimator?.cancel() 114 cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress, 115 if (showScanningAnimationNow) SHOW_CAMERA_PROTECTION_SCALE 116 else HIDDEN_CAMERA_PROTECTION_SCALE).apply { 117 startDelay = 118 if (showScanningAnim) 0 119 else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION 120 else PULSE_ERROR_DISAPPEAR_DURATION 121 duration = 122 if (showScanningAnim) CAMERA_PROTECTION_APPEAR_DURATION 123 else if (faceAuthSucceeded) CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION 124 else CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION 125 interpolator = 126 if (showScanningAnim) Interpolators.STANDARD_ACCELERATE 127 else if (faceAuthSucceeded) Interpolators.STANDARD 128 else Interpolators.STANDARD_DECELERATE 129 addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress) 130 addListener(object : AnimatorListenerAdapter() { 131 override fun onAnimationEnd(animation: Animator) { 132 cameraProtectionAnimator = null 133 if (!showScanningAnim) { 134 hide() 135 } 136 } 137 }) 138 } 139 140 rimAnimator?.cancel() 141 rimAnimator = if (showScanningAnim) { 142 createFaceScanningRimAnimator() 143 } else if (faceAuthSucceeded) { 144 createFaceSuccessRimAnimator() 145 } else { 146 createFaceNotSuccessRimAnimator() 147 } 148 rimAnimator?.apply { 149 addListener(object : AnimatorListenerAdapter() { 150 override fun onAnimationEnd(animation: Animator) { 151 rimAnimator = null 152 if (!showScanningAnim) { 153 requestLayout() 154 } 155 } 156 }) 157 } 158 rimAnimator?.start() 159 } 160 updateVisOnUpdateCutoutnull161 override fun updateVisOnUpdateCutout(): Boolean { 162 return false // instead, we always update the visibility whenever face scanning starts/ends 163 } 164 updateProtectionBoundingPathnull165 override fun updateProtectionBoundingPath() { 166 super.updateProtectionBoundingPath() 167 rimRect.set(protectionRect) 168 rimRect.scale(rimProgress) 169 } 170 onMeasurenull171 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 172 if (mBounds.isEmpty()) { 173 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 174 return 175 } 176 if (showScanningAnim) { 177 // Make sure that our measured height encompasses the extra space for the animation 178 mTotalBounds.set(mBoundingRect) 179 mTotalBounds.union( 180 rimRect.left.toInt(), 181 rimRect.top.toInt(), 182 rimRect.right.toInt(), 183 rimRect.bottom.toInt()) 184 val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0) 185 val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0) 186 logger.boundingRect(rimRect, "onMeasure: Face scanning animation") 187 logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect") 188 logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds") 189 logger.onMeasureDimensions(widthMeasureSpec, 190 heightMeasureSpec, 191 measuredWidth, 192 measuredHeight) 193 setMeasuredDimension(measuredWidth, measuredHeight) 194 } else { 195 setMeasuredDimension( 196 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), 197 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)) 198 } 199 } 200 drawFaceScanningRimnull201 private fun drawFaceScanningRim(canvas: Canvas) { 202 val rimPath = Path(protectionPath) 203 scalePath(rimPath, rimProgress) 204 rimPaint.style = Paint.Style.FILL 205 val rimPaintAlpha = rimPaint.alpha 206 rimPaint.color = ColorUtils.blendARGB( 207 faceScanningAnimColor, 208 Color.WHITE, 209 statusBarStateController.dozeAmount 210 ) 211 rimPaint.alpha = rimPaintAlpha 212 canvas.drawPath(rimPath, rimPaint) 213 } 214 drawCameraProtectionnull215 private fun drawCameraProtection(canvas: Canvas) { 216 val scaledProtectionPath = Path(protectionPath) 217 scalePath(scaledProtectionPath, cameraProtectionProgress) 218 paint.style = Paint.Style.FILL 219 paint.color = cameraProtectionColor 220 canvas.drawPath(scaledProtectionPath, paint) 221 } 222 createFaceSuccessRimAnimatornull223 private fun createFaceSuccessRimAnimator(): AnimatorSet { 224 val rimSuccessAnimator = AnimatorSet() 225 rimSuccessAnimator.playTogether( 226 createRimDisappearAnimator( 227 PULSE_RADIUS_SUCCESS, 228 PULSE_SUCCESS_DISAPPEAR_DURATION, 229 Interpolators.STANDARD_DECELERATE 230 ), 231 createSuccessOpacityAnimator(), 232 ) 233 return AnimatorSet().apply { 234 playTogether(rimSuccessAnimator, cameraProtectionAnimator) 235 } 236 } 237 createFaceNotSuccessRimAnimatornull238 private fun createFaceNotSuccessRimAnimator(): AnimatorSet { 239 return AnimatorSet().apply { 240 playTogether( 241 createRimDisappearAnimator( 242 SHOW_CAMERA_PROTECTION_SCALE, 243 PULSE_ERROR_DISAPPEAR_DURATION, 244 Interpolators.STANDARD 245 ), 246 cameraProtectionAnimator, 247 ) 248 } 249 } 250 createRimDisappearAnimatornull251 private fun createRimDisappearAnimator( 252 endValue: Float, 253 animDuration: Long, 254 timeInterpolator: TimeInterpolator 255 ): ValueAnimator { 256 return ValueAnimator.ofFloat(rimProgress, endValue).apply { 257 duration = animDuration 258 interpolator = timeInterpolator 259 addUpdateListener(this@FaceScanningOverlay::updateRimProgress) 260 addListener(object : AnimatorListenerAdapter() { 261 override fun onAnimationEnd(animation: Animator) { 262 rimProgress = HIDDEN_RIM_SCALE 263 invalidate() 264 } 265 }) 266 } 267 } 268 createSuccessOpacityAnimatornull269 private fun createSuccessOpacityAnimator(): ValueAnimator { 270 return ValueAnimator.ofInt(255, 0).apply { 271 duration = PULSE_SUCCESS_DISAPPEAR_DURATION 272 interpolator = Interpolators.LINEAR 273 addUpdateListener(this@FaceScanningOverlay::updateRimAlpha) 274 addListener(object : AnimatorListenerAdapter() { 275 override fun onAnimationEnd(animation: Animator) { 276 rimPaint.alpha = 255 277 invalidate() 278 } 279 }) 280 } 281 } 282 createFaceScanningRimAnimatornull283 private fun createFaceScanningRimAnimator(): AnimatorSet { 284 return AnimatorSet().apply { 285 playSequentially( 286 cameraProtectionAnimator, 287 createRimAppearAnimator(), 288 ) 289 } 290 } 291 createRimAppearAnimatornull292 private fun createRimAppearAnimator(): ValueAnimator { 293 return ValueAnimator.ofFloat( 294 SHOW_CAMERA_PROTECTION_SCALE, 295 PULSE_RADIUS_OUT 296 ).apply { 297 duration = PULSE_APPEAR_DURATION 298 interpolator = Interpolators.STANDARD_DECELERATE 299 addUpdateListener(this@FaceScanningOverlay::updateRimProgress) 300 } 301 } 302 hidenull303 private fun hide() { 304 visibility = INVISIBLE 305 hideOverlayRunnable?.run() 306 hideOverlayRunnable = null 307 requestLayout() 308 } 309 updateRimProgressnull310 private fun updateRimProgress(animator: ValueAnimator) { 311 rimProgress = animator.animatedValue as Float 312 invalidate() 313 } 314 updateCameraProtectionProgressnull315 private fun updateCameraProtectionProgress(animator: ValueAnimator) { 316 cameraProtectionProgress = animator.animatedValue as Float 317 invalidate() 318 } 319 updateRimAlphanull320 private fun updateRimAlpha(animator: ValueAnimator) { 321 rimPaint.alpha = animator.animatedValue as Int 322 invalidate() 323 } 324 325 companion object { 326 private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE 327 private const val SHOW_CAMERA_PROTECTION_SCALE = 1f 328 329 private const val PULSE_RADIUS_OUT = 1.125f 330 private const val PULSE_RADIUS_SUCCESS = 1.25f 331 332 private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L 333 private const val PULSE_APPEAR_DURATION = 250L // without start delay 334 335 private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L 336 private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay 337 338 private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L 339 private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay 340 scalePathnull341 private fun scalePath(path: Path, scalingFactor: Float) { 342 val scaleMatrix = Matrix().apply { 343 val boundingRectangle = RectF() 344 path.computeBounds(boundingRectangle, true) 345 setScale( 346 scalingFactor, scalingFactor, 347 boundingRectangle.centerX(), boundingRectangle.centerY() 348 ) 349 } 350 path.transform(scaleMatrix) 351 } 352 } 353 dumpnull354 override fun dump(pw: PrintWriter) { 355 val ipw = pw.asIndenting() 356 ipw.increaseIndent() 357 ipw.println("FaceScanningOverlay:") 358 super.dump(ipw) 359 ipw.println("rimProgress=$rimProgress") 360 ipw.println("rimRect=$rimRect") 361 ipw.println("this=$this") 362 ipw.decreaseIndent() 363 } 364 } 365