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.Animator 19 import android.animation.AnimatorListenerAdapter 20 import android.animation.AnimatorSet 21 import android.animation.ValueAnimator 22 import android.content.Context 23 import android.graphics.Canvas 24 import android.graphics.Paint 25 import android.graphics.PointF 26 import android.util.AttributeSet 27 import android.view.View 28 import android.view.animation.PathInterpolator 29 import com.android.internal.graphics.ColorUtils 30 import com.android.systemui.statusbar.LightRevealScrim 31 import com.android.systemui.statusbar.charging.RippleShader 32 33 private const val RIPPLE_ANIMATION_DURATION: Long = 1533 34 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f 35 36 /** 37 * Expanding ripple effect on the transition from biometric authentication success to showing 38 * launcher. 39 */ 40 class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 41 private var rippleInProgress: Boolean = false 42 private val rippleShader = RippleShader() 43 private val ripplePaint = Paint() 44 private var radius: Float = 0.0f 45 set(value) { 46 rippleShader.radius = value 47 field = value 48 } 49 private var origin: PointF = PointF() 50 set(value) { 51 rippleShader.origin = value 52 field = value 53 } 54 55 init { 56 rippleShader.color = 0xffffffff.toInt() // default color 57 rippleShader.progress = 0f 58 rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH 59 ripplePaint.shader = rippleShader 60 visibility = GONE 61 } 62 63 fun setSensorLocation(location: PointF) { 64 origin = location 65 radius = maxOf(location.x, location.y, width - location.x, height - location.y) 66 .toFloat() 67 } 68 69 fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { 70 if (rippleInProgress) { 71 return // Ignore if ripple effect is already playing 72 } 73 74 val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { 75 interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) 76 duration = RIPPLE_ANIMATION_DURATION 77 addUpdateListener { animator -> 78 val now = animator.currentPlayTime 79 rippleShader.progress = animator.animatedValue as Float 80 rippleShader.time = now.toFloat() 81 82 lightReveal?.revealAmount = animator.animatedValue as Float 83 invalidate() 84 } 85 } 86 87 val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { 88 interpolator = rippleAnimator.interpolator 89 startDelay = 10 90 duration = rippleAnimator.duration 91 addUpdateListener { animator -> 92 lightReveal?.revealAmount = animator.animatedValue as Float 93 } 94 } 95 96 val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { 97 duration = 167 98 addUpdateListener { animator -> 99 rippleShader.color = ColorUtils.setAlphaComponent( 100 rippleShader.color, 101 animator.animatedValue as Int 102 ) 103 invalidate() 104 } 105 } 106 107 val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { 108 startDelay = 417 109 duration = 1116 110 addUpdateListener { animator -> 111 rippleShader.color = ColorUtils.setAlphaComponent( 112 rippleShader.color, 113 animator.animatedValue as Int 114 ) 115 invalidate() 116 } 117 } 118 119 val animatorSet = AnimatorSet().apply { 120 playTogether( 121 rippleAnimator, 122 revealAnimator, 123 alphaInAnimator, 124 alphaOutAnimator 125 ) 126 addListener(object : AnimatorListenerAdapter() { 127 override fun onAnimationStart(animation: Animator?) { 128 rippleInProgress = true 129 visibility = VISIBLE 130 } 131 132 override fun onAnimationEnd(animation: Animator?) { 133 onAnimationEnd?.run() 134 rippleInProgress = false 135 visibility = GONE 136 } 137 }) 138 } 139 animatorSet.start() 140 } 141 142 fun setColor(color: Int) { 143 rippleShader.color = color 144 } 145 146 override fun onDraw(canvas: Canvas?) { 147 // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover 148 // the active effect area. Values here should be kept in sync with the 149 // animation implementation in the ripple shader. 150 val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * 151 (1 - rippleShader.progress)) * radius * 1.5f 152 canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) 153 } 154 } 155