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

Commit 4562e8e2 authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Add new touch effect to player

Test: manual
Bug: 155796337
Change-Id: I6e0c20522ee99c99d0b1a9f77376c5ca9838703b
parent 559130df
Loading
Loading
Loading
Loading
+6 −10
Original line number Diff line number Diff line
@@ -14,13 +14,9 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="?android:attr/colorBackgroundFloating" />
    <corners
        android:bottomLeftRadius="@dimen/qs_media_corner_radius"
        android:topLeftRadius="@dimen/qs_media_corner_radius"
        android:bottomRightRadius="@dimen/qs_media_corner_radius"
        android:topRightRadius="@dimen/qs_media_corner_radius"
    />
</shape>
 No newline at end of file
<com.android.systemui.media.IlluminationDrawable
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    systemui:rippleMinSize="30dp"
    systemui:rippleMaxSize="135dp"
    systemui:highlight="15"
    systemui:cornerRadius="@dimen/qs_media_corner_radius" />
 No newline at end of file
+7 −0
Original line number Diff line number Diff line
@@ -154,5 +154,12 @@
    <declare-styleable name="CaptionsToggleImageButton">
        <attr name="optedOut" format="boolean" />
    </declare-styleable>

    <declare-styleable name="IlluminationDrawable">
        <attr name="highlight" format="integer" />
        <attr name="cornerRadius" format="dimension" />
        <attr name="rippleMinSize" format="dimension" />
        <attr name="rippleMaxSize" format="dimension" />
    </declare-styleable>
</resources>
+190 −0
Original line number Diff line number Diff line
package com.android.systemui.media

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RadialGradient
import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.MathUtils
import android.util.MathUtils.lerp
import androidx.annotation.Keep
import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.ColorUtils.blendARGB
import com.android.systemui.Interpolators
import com.android.systemui.R
import org.xmlpull.v1.XmlPullParser

private const val BACKGROUND_ANIM_DURATION = 370L
private const val RIPPLE_ANIM_DURATION = 800L
private val GRADIENT_STOPS = floatArrayOf(0.2f, 1f)

private data class RippleData(
    var x: Float,
    var y: Float,
    var alpha: Float,
    var progress: Float,
    var minSize: Float,
    var maxSize: Float,
    var highlight: Float
)

/**
 * Drawable that can draw an animated gradient when tapped.
 */
@Keep
class IlluminationDrawable : Drawable() {

    private var cornerRadius = 0f
    private var highlightColor = Color.TRANSPARENT
    private val rippleData = RippleData(0f, 0f, 0f, 0f, 0f, 0f, 0f)
    private var tmpHsl = floatArrayOf(0f, 0f, 0f)
    private var paint = Paint()

    var backgroundColor = Color.TRANSPARENT
    set(value) {
        if (value == field) {
            return
        }
        field = value
        animateBackground()
    }

    private var rippleAnimation: AnimatorSet? = null
    private var backgroundAnimation: ValueAnimator? = null

    /**
     * Draw background and gradient.
     */
    override fun draw(canvas: Canvas) {
        paint.shader = if (rippleData.progress > 0) {
            val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
            val centerColor = blendARGB(paint.color, highlightColor, rippleData.alpha)
            RadialGradient(rippleData.x, rippleData.y, radius, intArrayOf(centerColor, paint.color),
                    GRADIENT_STOPS, Shader.TileMode.CLAMP)
        } else {
            null
        }
        canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
                cornerRadius, cornerRadius, paint)
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSPARENT
    }

    override fun inflate(
        r: Resources,
        parser: XmlPullParser,
        attrs: AttributeSet,
        theme: Resources.Theme?
    ) {
        val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
        cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
        rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
        rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
        rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
        a.recycle()
    }

    override fun setColorFilter(p0: ColorFilter?) {
        throw UnsupportedOperationException("Color filters are not supported")
    }

    override fun setAlpha(value: Int) {
        throw UnsupportedOperationException("Alpha is not supported")
    }

    /**
     * Cross fade background.
     * @see setTintList
     * @see backgroundColor
     */
    private fun animateBackground() {
        ColorUtils.colorToHSL(backgroundColor, tmpHsl)
        val L = tmpHsl[2]
        tmpHsl[2] = MathUtils.constrain(if (L < 1f - rippleData.highlight) {
            L + rippleData.highlight
        } else {
            L - rippleData.highlight
        }, 0f, 1f)

        val initialBackground = paint.color
        val initialHighlight = highlightColor
        val finalHighlight = ColorUtils.HSLToColor(tmpHsl)

        backgroundAnimation?.cancel()
        backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
            duration = BACKGROUND_ANIM_DURATION
            interpolator = Interpolators.FAST_OUT_LINEAR_IN
            addUpdateListener {
                val progress = it.animatedValue as Float
                paint.color = blendARGB(initialBackground, backgroundColor, progress)
                highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
                invalidateSelf()
            }
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    backgroundAnimation = null
                }
            })
            start()
        }
    }

    override fun setTintList(tint: ColorStateList?) {
        super.setTintList(tint)
        backgroundColor = tint!!.defaultColor
    }

    /**
     * Draws an animated ripple that expands from {@param x} and  {@param y} fading away.
     *
     * @param x X position of gradient centroid.
     * @param y Y position of gradient centroid.
     */
    fun illuminate(x: Int, y: Int) {
        rippleData.x = x.toFloat()
        rippleData.y = y.toFloat()
        rippleData.alpha = 1f
        rippleData.progress = 0f
        invalidateSelf()

        rippleAnimation?.cancel()
        rippleAnimation = AnimatorSet().apply {
            playTogether(ValueAnimator.ofFloat(1f, 0f).apply {
                startDelay = 133
                duration = RIPPLE_ANIM_DURATION - startDelay
                interpolator = Interpolators.LINEAR_OUT_SLOW_IN
                addUpdateListener {
                    rippleData.alpha = it.animatedValue as Float
                    invalidateSelf()
                }
            }, ValueAnimator.ofFloat(0f, 1f).apply {
                duration = RIPPLE_ANIM_DURATION
                interpolator = Interpolators.LINEAR_OUT_SLOW_IN
                addUpdateListener {
                    rippleData.progress = it.animatedValue as Float
                    invalidateSelf()
                }
            })
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    rippleData.progress = 0f
                    rippleAnimation = null
                    invalidateSelf()
                }
            })
            start()
        }
    }
}
 No newline at end of file
+12 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.MediaDescription;
@@ -38,6 +39,7 @@ import android.widget.TextView;

import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.R;
import com.android.systemui.media.IlluminationDrawable;
import com.android.systemui.media.MediaControlPanel;
import com.android.systemui.media.SeekBarObserver;
import com.android.systemui.media.SeekBarViewModel;
@@ -173,7 +175,7 @@ public class QSMediaPlayer extends MediaControlPanel {
            LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
            int i = 0;
            for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
                ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
                final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
                ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
                if (thatBtn == null || thatBtn.getDrawable() == null
                        || thatBtn.getVisibility() != View.VISIBLE) {
@@ -187,6 +189,15 @@ public class QSMediaPlayer extends MediaControlPanel {
                thisBtn.setOnClickListener(v -> {
                    Log.d(TAG, "clicking on other button");
                    thatBtn.performClick();
                    if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
                        Rect mediaRect = new Rect();
                        Rect buttonRect = new Rect();
                        mMediaNotifView.getGlobalVisibleRect(mediaRect);
                        thisBtn.getGlobalVisibleRect(buttonRect);
                        ((IlluminationDrawable) mMediaNotifView.getBackground()).illuminate(
                                buttonRect.centerX() - mediaRect.left,
                                buttonRect.centerY() - mediaRect.top);
                    }
                });
            }