From 3f535f94fa1a33f7a408c849be2372939808a45d Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Thu, 20 May 2021 18:36:40 +0100 Subject: [PATCH] Add haptic feedback to auth ripple animation Add a vibration feedback composed of LOW_TICK primitives with decaying intensity. The composition is played together with the ripple animation that is triggered after a fingerprint or face authentication is successfully made. The existing vibrator from AuthenticationClient on success was kept, and the new feedback is synchronized with the animation that plays afterwards. Fix: 182381499 Test: manual Change-Id: I0f023217ad538d1f66126f7e053e367ae2b0559f --- .../systemui/biometrics/AuthRippleView.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 374ddaedb405a..01fbe398f8b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -23,7 +23,11 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.PointF +import android.media.AudioAttributes +import android.os.VibrationEffect +import android.os.Vibrator import android.util.AttributeSet +import android.util.MathUtils import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils @@ -31,15 +35,26 @@ import com.android.systemui.statusbar.charging.RippleShader private const val RIPPLE_ANIMATION_DURATION: Long = 1533 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f +private const val RIPPLE_VIBRATION_PRIMITIVE: Int = VibrationEffect.Composition.PRIMITIVE_LOW_TICK +private const val RIPPLE_VIBRATION_SIZE: Int = 60 +private const val RIPPLE_VIBRATION_SCALE_START: Float = 0.6f +private const val RIPPLE_VIBRATION_SCALE_DECAY: Float = -0.1f /** * Expanding ripple effect on the transition from biometric authentication success to showing * launcher. */ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { + private val vibrator: Vibrator? = context?.getSystemService(Vibrator::class.java) private var rippleInProgress: Boolean = false private val rippleShader = RippleShader() private val ripplePaint = Paint() + private val rippleVibrationEffect = createVibrationEffect(vibrator) + private val rippleVibrationAttrs = + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build() init { rippleShader.color = 0xffffffff.toInt() // default color @@ -95,6 +110,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at visibility = GONE } }) + vibrate() animatorSet.start() visibility = VISIBLE rippleInProgress = true @@ -108,4 +124,23 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at // draw over the entire screen canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint) } + + private fun vibrate() { + if (rippleVibrationEffect != null) { + vibrator?.vibrate(rippleVibrationEffect, rippleVibrationAttrs) + } + } + + private fun createVibrationEffect(vibrator: Vibrator?): VibrationEffect? { + if (vibrator?.areAllPrimitivesSupported(RIPPLE_VIBRATION_PRIMITIVE) == false) { + return null + } + val composition = VibrationEffect.startComposition() + for (i in 0 until RIPPLE_VIBRATION_SIZE) { + val scale = + RIPPLE_VIBRATION_SCALE_START * MathUtils.exp(RIPPLE_VIBRATION_SCALE_DECAY * i) + composition.addPrimitive(RIPPLE_VIBRATION_PRIMITIVE, scale, 0 /* delay */) + } + return composition.compose() + } }