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

Commit 12ea0fe9 authored by Lucas Dupin's avatar Lucas Dupin Committed by Android (Google) Code Review
Browse files

Merge "Add new touch effect to player" into rvc-dev

parents ce9b4785 4562e8e2
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);
                    }
                });
            }