Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f535f94 authored by Lais Andrade's avatar Lais Andrade
Browse files

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
parent 33f631ad
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -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()
    }
}