• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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