diff options
| author | 2023-02-21 18:31:16 +0000 | |
|---|---|---|
| committer | 2023-02-21 18:31:16 +0000 | |
| commit | fb0dd291214513b39d6373bd07a598088c2d819c (patch) | |
| tree | 087d2a3785c606ce8f07901014336393820a1e2f | |
| parent | 54639353d6b964e789bfb78c4f1c4e00ca70f47e (diff) | |
| parent | 657aa093927ba2ec441d5a00d4486beb8b2ea9fd (diff) | |
Merge changes from topic "polish-docking-anim" into tm-qpr-dev am: 657aa09392
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21452247
Change-Id: I8c4a47060cb9009f70e5560e15762b51567f6a1e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
8 files changed, 443 insertions, 60 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index 442c6fadb7c1..bd91c65ecc6e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt @@ -68,7 +68,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) { private fun applyConfigToShader() { with(rippleShader) { setCenter(config.centerX, config.centerY) - setMaxSize(config.maxWidth, config.maxHeight) + rippleSize.setMaxSize(config.maxWidth, config.maxHeight) pixelDensity = config.pixelDensity color = ColorUtils.setAlphaComponent(config.color, config.opacity) sparkleStrength = config.sparkleStrength diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 61ca90a297fe..aad2d70f5794 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt @@ -15,9 +15,10 @@ */ package com.android.systemui.surfaceeffects.ripple -import android.graphics.PointF import android.graphics.RuntimeShader +import android.util.Log import android.util.MathUtils +import androidx.annotation.VisibleForTesting import com.android.systemui.animation.Interpolators import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary @@ -44,6 +45,8 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : } // language=AGSL companion object { + private val TAG = RippleShader::class.simpleName + // Default fade in/ out values. The value range is [0,1]. const val DEFAULT_FADE_IN_START = 0f const val DEFAULT_FADE_OUT_END = 1f @@ -99,7 +102,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : vec4 main(vec2 p) { float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius, in_thickness), in_blur); - float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius), + float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius), in_blur); float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) * (1.-sparkleRing) * in_fadeSparkle; @@ -184,12 +187,17 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_center", x, y) } - /** Max width of the ripple. */ - private var maxSize: PointF = PointF() - fun setMaxSize(width: Float, height: Float) { - maxSize.x = width - maxSize.y = height - } + /** + * Blur multipliers for the ripple. + * + * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to + * add more blur. + */ + var blurStart: Float = 1.25f + var blurEnd: Float = 0.5f + + /** Size of the ripple. */ + val rippleSize = RippleSize() /** * Linear progress of the ripple. Float value between [0, 1]. @@ -209,15 +217,19 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** Progress with Standard easing curve applied. */ private var progress: Float = 0.0f set(value) { - currentWidth = maxSize.x * value - currentHeight = maxSize.y * value - setFloatUniform("in_size", currentWidth, currentHeight) + field = value + + rippleSize.update(value) - setFloatUniform("in_thickness", maxSize.y * value * 0.5f) - // radius should not exceed width and height values. - setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value) + setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight) + setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f) + // Corner radius is always max of the min between the current width and height. + setFloatUniform( + "in_cornerRadius", + Math.min(rippleSize.currentWidth, rippleSize.currentHeight) + ) - setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) + setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value)) } /** Play time since the start of the effect. */ @@ -264,12 +276,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_pixelDensity", value) } - var currentWidth: Float = 0f - private set - - var currentHeight: Float = 0f - private set - /** Parameters that are used to fade in/ out of the sparkle ring. */ val sparkleRingFadeParams = FadeParams( @@ -342,4 +348,101 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** The endpoint of the fade out, given that the animation goes from 0 to 1. */ var fadeOutEnd: Float = DEFAULT_FADE_OUT_END, ) + + /** + * Desired size of the ripple at a point t in [progress]. + * + * <p>Note that [progress] is curved and normalized. Below is an example usage: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height= + * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f) + * + * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height= + * maxHeight) + */ + data class SizeAtProgress( + /** Time t in [0,1] progress range. */ + var t: Float, + /** Target width size of the ripple at time [t]. */ + var width: Float, + /** Target height size of the ripple at time [t]. */ + var height: Float + ) + + /** Updates and stores the ripple size. */ + inner class RippleSize { + @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>() + @VisibleForTesting var currentSizeIndex = 0 + @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f) + + var currentWidth: Float = 0f + private set + var currentHeight: Float = 0f + private set + + /** + * Sets the max size of the ripple. + * + * <p>Use this if the ripple shape simply changes linearly. + */ + fun setMaxSize(width: Float, height: Float) { + setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height)) + } + + /** + * Sets the list of [sizes]. + * + * <p>Note that setting this clears the existing sizes. + */ + fun setSizeAtProgresses(vararg sizes: SizeAtProgress) { + // Reset everything. + this.sizes.clear() + currentSizeIndex = 0 + + this.sizes.addAll(sizes) + this.sizes.sortBy { it.t } + } + + /** + * Updates the current ripple size based on the progress. + * + * <p>Should be called when progress updates. + */ + fun update(progress: Float) { + val targetIndex = updateTargetIndex(progress) + val prevIndex = Math.max(targetIndex - 1, 0) + + val targetSize = sizes[targetIndex] + val prevSize = sizes[prevIndex] + + val subProgress = subProgress(prevSize.t, targetSize.t, progress) + + currentWidth = targetSize.width * subProgress + prevSize.width + currentHeight = targetSize.height * subProgress + prevSize.height + } + + private fun updateTargetIndex(progress: Float): Int { + if (sizes.isEmpty()) { + // It could be empty on init. + if (progress > 0f) { + Log.e( + TAG, + "Did you forget to set the ripple size? Use [setMaxSize] or " + + "[setSizeAtProgresses] before playing the animation." + ) + } + // If there's no size is set, we set everything to 0. + setSizeAtProgresses(initialSize) + } + + var candidate = sizes[currentSizeIndex] + + while (progress > candidate.t) { + currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1) + candidate = sizes[currentSizeIndex] + } + + return currentSizeIndex + } + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index 3c9328c82b83..4c7c43548016 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt @@ -45,12 +45,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a var duration: Long = 1750 - private var maxWidth: Float = 0.0f - private var maxHeight: Float = 0.0f fun setMaxSize(maxWidth: Float, maxHeight: Float) { - this.maxWidth = maxWidth - this.maxHeight = maxHeight - rippleShader.setMaxSize(maxWidth, maxHeight) + rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight) } private var centerX: Float = 0.0f @@ -84,6 +80,106 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a ripplePaint.shader = rippleShader } + /** + * Sets the fade parameters for the base ring. + * + * <p>Base ring indicates a blurred ring below the sparkle ring. See + * [RippleShader.baseRingFadeParams]. + */ + @JvmOverloads + fun setBaseRingFadeParams( + fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.baseRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the sparkle ring. + * + * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See + * [RippleShader.sparkleRingFadeParams]. + */ + @JvmOverloads + fun setSparkleRingFadeParams( + fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.sparkleRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the center fill. + * + * <p>One common use case is set all the params to 1, which completely removes the center fill. + * See [RippleShader.centerFillFadeParams]. + */ + @JvmOverloads + fun setCenterFillFadeParams( + fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.centerFillFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + private fun setFadeParams( + fadeParams: RippleShader.FadeParams, + fadeInStart: Float, + fadeInEnd: Float, + fadeOutStart: Float, + fadeOutEnd: Float + ) { + with(fadeParams) { + this.fadeInStart = fadeInStart + this.fadeInEnd = fadeInEnd + this.fadeOutStart = fadeOutStart + this.fadeOutEnd = fadeOutEnd + } + } + + /** + * Sets blur multiplier at start and end of the progress. + * + * <p>It interpolates between [start] and [end]. No need to set it if using default blur. + */ + fun setBlur(start: Float, end: Float) { + rippleShader.blurStart = start + rippleShader.blurEnd = end + } + + /** + * Sets the list of [RippleShader.SizeAtProgress]. + * + * <p>Note that this clears the list before it sets with the new data. + */ + fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) { + rippleShader.rippleSize.setSizeAtProgresses(*targetSizes) + } + @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (animator.isRunning) { @@ -133,13 +229,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a } // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. (Twice bigger) + // in the ripple shader. if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = rippleShader.currentWidth + val maskRadius = rippleShader.rippleSize.currentWidth canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) - } else { - val maskWidth = rippleShader.currentWidth * 2 - val maskHeight = rippleShader.currentHeight * 2 + } else if (rippleShape == RippleShape.ELLIPSE) { + val maskWidth = rippleShader.rippleSize.currentWidth * 2 + val maskHeight = rippleShader.rippleSize.currentHeight * 2 canvas.drawRect( /* left= */ centerX - maskWidth, /* top= */ centerY - maskHeight, @@ -147,6 +243,10 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a /* bottom= */ centerY + maskHeight, ripplePaint ) + } else { // RippleShape.RoundedBox + // No masking for the rounded box, as it has more blur which requires larger bounds. + // Masking creates sharp bounds even when the masking is 4 times bigger. + canvas.drawPaint(ripplePaint) } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 8b2f46648fef..78898932249b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt @@ -50,9 +50,9 @@ class SdfShaderLibrary { float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, float borderThickness) { - float outerRoundBox = sdRoundedBox(p, size, cornerRadius); - float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness), - cornerRadius - borderThickness); + float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness), + cornerRadius + borderThickness); + float innerRoundBox = sdRoundedBox(p, size, cornerRadius); return subtract(outerRoundBox, innerRoundBox); } """ @@ -69,10 +69,13 @@ class SdfShaderLibrary { vec2 u = wh*p, v = wh*wh; - float U1 = u.y/2.0; float U5 = 4.0*U1; - float U2 = v.y-v.x; float U6 = 6.0*U1; - float U3 = u.x-U2; float U7 = 3.0*U3; + float U1 = u.y/2.0; + float U2 = v.y-v.x; + float U3 = u.x-U2; float U4 = u.x+U2; + float U5 = 4.0*U1; + float U6 = 6.0*U1; + float U7 = 3.0*U3; float t = 0.5; for (int i = 0; i < 3; i ++) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 58b230f52e93..4b32759588e0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -75,7 +75,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } private var radius: Float = 0f set(value) { - rippleShader.setMaxSize(value * 2f, value * 2f) + rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f) field = value } private var origin: Point = Point() @@ -364,7 +364,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at if (drawRipple) { canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(), - rippleShader.currentWidth, ripplePaint) + rippleShader.rippleSize.currentWidth, ripplePaint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 36103f8db8d8..cef415c8a490 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -33,7 +33,9 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig; +import com.android.systemui.surfaceeffects.ripple.RippleShader; import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape; import com.android.systemui.surfaceeffects.ripple.RippleView; @@ -44,10 +46,12 @@ import java.text.NumberFormat; */ final class WirelessChargingLayout extends FrameLayout { private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500; - private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750; + private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 3000; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; + // This is only relevant to the rounded box ripple. + private RippleShader.SizeAtProgress[] mSizeAtProgressArray; WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, boolean isDozing, RippleShape rippleShape) { @@ -125,20 +129,23 @@ final class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); - ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); - scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); - ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); - scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); - scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE - ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) - - SCRIM_FADE_DURATION); - AnimatorSet animatorSetScrim = new AnimatorSet(); - animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); - animatorSetScrim.start(); + // For tablet docking animation, we don't play the background scrim. + if (!Utilities.isTablet(context)) { + ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); + scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); + ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); + scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); + scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE + ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) + - SCRIM_FADE_DURATION); + AnimatorSet animatorSetScrim = new AnimatorSet(); + animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); + animatorSetScrim.start(); + } mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); @@ -147,7 +154,26 @@ final class WirelessChargingLayout extends FrameLayout { if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); mRippleView.setSparkleStrength(0.22f); - mRippleView.setColor(color, 28); + mRippleView.setColor(color, 102); // 40% of opacity. + mRippleView.setBaseRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 0.47f + ); + mRippleView.setSparkleRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 1f + ); + mRippleView.setCenterFillFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0f, + /* fadeOutEnd= */ 0.2f + ); + mRippleView.setBlur(6.5f, 2.5f); } else { mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA); @@ -246,9 +272,7 @@ final class WirelessChargingLayout extends FrameLayout { int height = getMeasuredHeight(); mRippleView.setCenter(width * 0.5f, height * 0.5f); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { - // Those magic numbers are introduced for visual polish. This aspect ratio maps with - // the tablet's docking station. - mRippleView.setMaxSize(width * 1.36f, height * 1.46f); + updateRippleSizeAtProgressList(width, height); } else { float maxSize = Math.max(width, height); mRippleView.setMaxSize(maxSize, maxSize); @@ -257,4 +281,36 @@ final class WirelessChargingLayout extends FrameLayout { super.onLayout(changed, left, top, right, bottom); } + + private void updateRippleSizeAtProgressList(float width, float height) { + if (mSizeAtProgressArray == null) { + float maxSize = Math.max(width, height); + mSizeAtProgressArray = new RippleShader.SizeAtProgress[] { + // Those magic numbers are introduced for visual polish. It starts from a pill + // shape and expand to a full circle. + new RippleShader.SizeAtProgress(0f, 0f, 0f), + new RippleShader.SizeAtProgress(0.3f, width * 0.4f, height * 0.4f), + new RippleShader.SizeAtProgress(1f, maxSize, maxSize) + }; + } else { + // Same multipliers, just need to recompute with the new width and height. + RippleShader.SizeAtProgress first = mSizeAtProgressArray[0]; + first.setT(0f); + first.setWidth(0f); + first.setHeight(0f); + + RippleShader.SizeAtProgress second = mSizeAtProgressArray[1]; + second.setT(0.3f); + second.setWidth(width * 0.4f); + second.setHeight(height * 0.4f); + + float maxSize = Math.max(width, height); + RippleShader.SizeAtProgress last = mSizeAtProgressArray[2]; + last.setT(1f); + last.setWidth(maxSize); + last.setHeight(maxSize); + } + + mRippleView.setSizeAtProgresses(mSizeAtProgressArray); + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index 4ff082ad6e06..0b0535df6228 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -98,7 +98,7 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi // Calculates the actual starting percentage according to ripple shader progress set method. // Check calculations in [RippleShader.progress] fun calculateStartingPercentage(newHeight: Float): Float { - val ratio = rippleShader.currentHeight / newHeight + val ratio = rippleShader.rippleSize.currentHeight / newHeight val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat() return 1 - remainingPercentage } diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt new file mode 100644 index 000000000000..89cc18cc5d67 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.surfaceeffects.ripple + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RippleShaderTest : SysuiTestCase() { + + private lateinit var rippleShader: RippleShader + + @Before + fun setup() { + rippleShader = RippleShader() + } + + @Test + fun setMaxSize_hasCorrectSizes() { + val expectedMaxWidth = 300f + val expectedMaxHeight = 500f + + rippleShader.rippleSize.setMaxSize(expectedMaxWidth, expectedMaxHeight) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(2) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + val maxSize = rippleShader.rippleSize.sizes[1] + assertThat(maxSize.t).isEqualTo(1f) + assertThat(maxSize.width).isEqualTo(expectedMaxWidth) + assertThat(maxSize.height).isEqualTo(expectedMaxHeight) + } + + @Test + fun setSizeAtProgresses_hasCorrectSizes() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + + rippleShader.rippleSize.setSizeAtProgresses(expectedSize0, expectedSize1, expectedSize2) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(3) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + } + + @Test + fun setSizeAtProgresses_sizeListIsSortedByT() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + // Add them in unsorted order + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize3, + expectedSize2, + expectedSize4, + expectedSize1 + ) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(5) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + assertThat(rippleShader.rippleSize.sizes[3]).isEqualTo(expectedSize3) + assertThat(rippleShader.rippleSize.sizes[4]).isEqualTo(expectedSize4) + } + + @Test + fun update_getsCorrectNextTargetSize() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize1, + expectedSize2, + expectedSize3, + expectedSize4 + ) + + rippleShader.rippleSize.update(0.5f) + // Progress is between 0.4 and 0.8 (expectedSize3 and 4), so the index should be 3. + assertThat(rippleShader.rippleSize.currentSizeIndex).isEqualTo(3) + } + + @Test + fun update_sizeListIsEmpty_setsInitialSize() { + assertThat(rippleShader.rippleSize.sizes).isEmpty() + + rippleShader.rippleSize.update(0.3f) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(1) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + } +} |