Make RippleShader support circle and rounded box shapes.

There will be a follow up CL for adding a oval shape.
This CL breaks the RippleShader into smaller pieces
and glue only the neccessary parts together when
initializing.

Note that this CL, the rounded box shape is not being
used.

Bug: b/237282686
Test: Manual, WiredChargingRippleControllerTest

Change-Id: I09091a612e3307a76a0bc938ce0810153541243a
This commit is contained in:
Yein Jo
2022-07-21 16:19:51 +00:00
parent 3fce0b0313
commit a37c26f550
11 changed files with 336 additions and 135 deletions

View File

@@ -75,12 +75,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
private var radius: Float = 0f
set(value) {
rippleShader.radius = value
rippleShader.setMaxSize(value * 2f, value * 2f)
field = value
}
private var origin: PointF = PointF()
set(value) {
rippleShader.origin = value
rippleShader.setCenter(value.x, value.y)
field = value
}

View File

@@ -19,7 +19,6 @@ package com.android.systemui.charging
import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.PointF
import android.os.SystemProperties
import android.util.DisplayMetrics
import android.view.View
@@ -85,7 +84,7 @@ class WiredChargingRippleController @Inject constructor(
private var debounceLevel = 0
@VisibleForTesting
var rippleView: RippleView = RippleView(context, attrs = null)
var rippleView: RippleView = RippleView(context, attrs = null).also { it.setupShader() }
init {
pluggedIn = batteryController.isPluggedIn
@@ -177,20 +176,25 @@ class WiredChargingRippleController @Inject constructor(
context.display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
rippleView.radius = Integer.max(width, height).toFloat()
rippleView.origin = when (RotationUtils.getExactRotation(context)) {
val maxDiameter = Integer.max(width, height) * 2f
rippleView.setMaxSize(maxDiameter, maxDiameter)
when (RotationUtils.getExactRotation(context)) {
RotationUtils.ROTATION_LANDSCAPE -> {
PointF(width * normalizedPortPosY, height * (1 - normalizedPortPosX))
rippleView.setCenter(
width * normalizedPortPosY, height * (1 - normalizedPortPosX))
}
RotationUtils.ROTATION_UPSIDE_DOWN -> {
PointF(width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
rippleView.setCenter(
width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
}
RotationUtils.ROTATION_SEASCAPE -> {
PointF(width * (1 - normalizedPortPosY), height * normalizedPortPosX)
rippleView.setCenter(
width * (1 - normalizedPortPosY), height * normalizedPortPosX)
}
else -> {
// ROTATION_NONE
PointF(width * normalizedPortPosX, height * normalizedPortPosY)
rippleView.setCenter(
width * normalizedPortPosX, height * normalizedPortPosY)
}
}
}

View File

@@ -21,7 +21,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -34,6 +33,7 @@ import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.ripple.RippleShader;
import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -138,6 +138,8 @@ public class WirelessChargingLayout extends FrameLayout {
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
// TODO: Make rounded box shape if the device is tablet.
mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
@@ -230,11 +232,11 @@ public class WirelessChargingLayout extends FrameLayout {
if (mRippleView != null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
mRippleView.setColor(
Utils.getColorAttr(mRippleView.getContext(),
android.R.attr.colorAccent).getDefaultColor());
mRippleView.setOrigin(new PointF(width / 2, height / 2));
mRippleView.setRadius(Math.max(width, height) * 0.5f);
mRippleView.setCenter(width * 0.5f, height * 0.5f);
float maxSize = Math.max(width, height);
mRippleView.setMaxSize(maxSize, maxSize);
mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
android.R.attr.colorAccent).getDefaultColor());
}
super.onLayout(changed, left, top, right, bottom);

View File

@@ -19,7 +19,6 @@ package com.android.systemui.media.taptotransfer.receiver
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -201,10 +200,10 @@ class MediaTttChipControllerReceiver @Inject constructor(
val height = windowBounds.height()
val width = windowBounds.width()
rippleView.radius = height / 5f
val maxDiameter = height / 2.5f
rippleView.setMaxSize(maxDiameter, maxDiameter)
// Center the ripple on the bottom of the screen in the middle.
rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
rippleView.setCenter(width * 0.5f, height.toFloat())
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
rippleView.setColor(colorWithAlpha)

View File

@@ -23,10 +23,9 @@ import com.android.systemui.ripple.RippleView
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
class ReceiverChipRippleView(
context: Context?, attrs: AttributeSet?
) : RippleView(context, attrs) {
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
setupShader()
setRippleFill(true)
duration = 3000L
}

View File

@@ -20,92 +20,83 @@ import android.graphics.RuntimeShader
import android.util.MathUtils
/**
* Shader class that renders an expanding charging ripple effect. A charging ripple contains
* three elements:
* 1. an expanding filled circle that appears in the beginning and quickly fades away
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
*
* 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
* 2. an expanding ring that appears throughout the effect
* 3. an expanding ring-shaped area that reveals noise over #2.
*
* The ripple shader will be default to the circle shape if not specified.
*
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
class RippleShader internal constructor() : RuntimeShader(SHADER) {
class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
RuntimeShader(buildShader(rippleShape)) {
/** Shapes that the [RippleShader] supports. */
enum class RippleShape {
CIRCLE,
ROUNDED_BOX
}
companion object {
private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
private const val SHADER_UNIFORMS = """uniform vec2 in_center;
uniform vec2 in_size;
uniform float in_progress;
uniform float in_maxRadius;
uniform float in_cornerRadius;
uniform float in_thickness;
uniform float in_time;
uniform float in_distort_radial;
uniform float in_distort_xy;
uniform float in_radius;
uniform float in_fadeSparkle;
uniform float in_fadeCircle;
uniform float in_fadeFill;
uniform float in_fadeRing;
uniform float in_blur;
uniform float in_pixelDensity;
layout(color) uniform vec4 in_color;
uniform float in_sparkle_strength;"""
private const val SHADER_LIB = """float triangleNoise(vec2 n) {
n = fract(n * vec2(5.3987, 5.4421));
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
float xy = n.x * n.y;
return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
}
const float PI = 3.1415926535897932384626;
float threshold(float v, float l, float h) {
return step(l, v) * (1.0 - step(h, v));
}
private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
// distortion only applies to circle
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkles(vec2 uv, float t) {
float n = triangleNoise(uv);
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.01;
float h = l + 0.1;
float o = smoothstep(n - l, h, n);
o *= abs(sin(PI * o * (t + 0.55 * i)));
s += o;
}
return s;
}
float sparkleRing = softRing(p_distorted, in_center, radius, in_blur);
float inside = softCircle(p_distorted, in_center, radius * 1.2, in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
float blurHalf = blur * 0.5;
float d = distance(uv, xy);
return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
}
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
"""
float softRing(vec2 uv, vec2 xy, float radius, float blur) {
float thickness_half = radius * 0.25;
float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
return circle_outer - circle_inner;
}
private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
float sparkleRing = softRoundedBoxRing(p, in_center, in_size, in_cornerRadius,
in_thickness, in_blur);
float inside = softRoundedBox(p, in_center, in_size * 1.2, in_cornerRadius, in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
vec2 distort(vec2 p, vec2 origin, float time,
float distort_amount_radial, float distort_amount_xy) {
float2 distance = origin - p;
float angle = atan(distance.y, distance.x);
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
}"""
private const val SHADER_MAIN = """vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
in_distort_xy);
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
"""
// Draw shapes
float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* sparkleRing * in_fadeSparkle;
float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
float rippleAlpha = max(circle * in_fadeCircle,
softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}"""
private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
SdfShaderLibrary.CIRCLE_SDF + SHADER_CIRCLE_MAIN
private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.ROUNDED_BOX_SDF +
SHADER_ROUNDED_BOX_MAIN
private fun buildShader(rippleShape: RippleShape): String =
if (rippleShape == RippleShape.CIRCLE) CIRCLE_SHADER else ROUNDED_BOX_SHADER
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -116,22 +107,18 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) {
}
/**
* Maximum radius of the ripple.
* Sets the center position of the ripple.
*/
var radius: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_maxRadius", value)
}
fun setCenter(x: Float, y: Float) {
setFloatUniform("in_center", x, y)
}
/**
* Origin coordinate of the ripple.
*/
var origin: PointF = PointF()
set(value) {
field = value
setFloatUniform("in_origin", value.x, value.y)
}
/** Max width of the ripple. */
private var maxSize: PointF = PointF()
fun setMaxSize(width: Float, height: Float) {
maxSize.x = width
maxSize.y = height
}
/**
* Progress of the ripple. Float value between [0, 1].
@@ -140,20 +127,27 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) {
set(value) {
field = value
setFloatUniform("in_progress", value)
setFloatUniform("in_radius",
(1 - (1 - value) * (1 - value) * (1 - value))* radius)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
/* height= */ maxSize.y * curvedProg)
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
setFloatUniform("in_cornerRadius",
Math.min(maxSize.x, maxSize.y) * curvedProg)
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
var fadeOutRipple = 0f
var fadeCircle = 0f
var fadeFill = 0f
if (!rippleFill) {
fadeCircle = subProgress(0f, 0.2f, value)
fadeFill = subProgress(0f, 0.6f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
setFloatUniform("in_fadeCircle", 1 - fadeCircle)
setFloatUniform("in_fadeFill", 1 - fadeFill)
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
@@ -169,7 +163,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) {
/**
* A hex value representing the ripple color, in the format of ARGB
*/
var color: Int = 0xffffff.toInt()
var color: Int = 0xffffff
set(value) {
field = value
setColorUniform("in_color", value)

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 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.ripple
/** A common utility functions that are used for computing [RippleShader]. */
class RippleShaderUtilLibrary {
companion object {
const val SHADER_LIB = """float triangleNoise(vec2 n) {
n = fract(n * vec2(5.3987, 5.4421));
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
float xy = n.x * n.y;
return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
}
const float PI = 3.1415926535897932384626;
float sparkles(vec2 uv, float t) {
float n = triangleNoise(uv);
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.01;
float h = l + 0.1;
float o = smoothstep(n - l, h, n);
o *= abs(sin(PI * o * (t + 0.55 * i)));
s += o;
}
return s;
}
vec2 distort(vec2 p, float time, float distort_amount_radial,
float distort_amount_xy) {
float angle = atan(p.y, p.x);
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
}"""
}
}

View File

@@ -23,39 +23,42 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
import com.android.systemui.ripple.RippleShader.RippleShape
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
/**
* A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
* then call [startRipple].
* A generic expanding ripple effect.
*
* Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
* then call [startRipple] to trigger the ripple expansion.
*/
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val rippleShader = RippleShader()
private val defaultColor: Int = 0xffffffff.toInt()
private lateinit var rippleShader: RippleShader
private lateinit var rippleShape: RippleShape
private val ripplePaint = Paint()
var rippleInProgress: Boolean = false
var radius: Float = 0.0f
set(value) {
rippleShader.radius = value
field = value
}
var origin: PointF = PointF()
set(value) {
rippleShader.origin = value
field = value
}
var duration: Long = 1750
init {
rippleShader.color = defaultColor
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
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)
}
private var centerX: Float = 0.0f
private var centerY: Float = 0.0f
fun setCenter(x: Float, y: Float) {
this.centerX = x
this.centerY = y
rippleShader.setCenter(x, y)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -68,6 +71,18 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
super.onAttachedToWindow()
}
/** Initializes the shader. Must be called before [startRipple]. */
fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
this.rippleShape = rippleShape
rippleShader = RippleShader(rippleShape)
rippleShader.color = RIPPLE_DEFAULT_COLOR
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
}
@JvmOverloads
fun startRipple(onAnimationEnd: Runnable? = null) {
if (rippleInProgress) {
@@ -113,11 +128,24 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
// if it's unsupported.
return
}
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * radius * 2
canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
// 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.
if (rippleShape == RippleShape.CIRCLE) {
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * maxWidth
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
} else {
val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * maxWidth * 2
val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * maxHeight * 2
canvas.drawRect(
/* left= */ centerX - maskWidth,
/* top= */ centerY - maskHeight,
/* right= */ centerX + maskWidth,
/* bottom= */ centerY + maskHeight,
ripplePaint)
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2022 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.ripple
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
companion object {
const val CIRCLE_SDF = """
float sdCircle(vec2 p, float r) {
return (length(p)-r) / r;
}
float softCircle(vec2 p, vec2 origin, float radius, float blur) {
float d = sdCircle(p-origin, radius);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
float softRing(vec2 p, vec2 origin, float radius, float blur) {
float thicknessHalf = radius * 0.25;
float outerCircle = sdCircle(p-origin, radius + thicknessHalf);
float innerCircle = sdCircle(p-origin, radius);
float d = max(outerCircle, -innerCircle);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
"""
const val ROUNDED_BOX_SDF = """
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
size *= 0.5;
cornerRadius *= 0.5;
vec2 d = abs(p)-size+cornerRadius;
float outside = length(max(d, 0.0));
float inside = min(max(d.x, d.y), 0.0);
return (outside+inside-cornerRadius)/size.y;
}
float softRoundedBox(vec2 p, vec2 origin, vec2 size, float cornerRadius, float blur) {
float d = sdRoundedBox(p-origin, size, cornerRadius);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
float softRoundedBoxRing(vec2 p, vec2 origin, vec2 size, float cornerRadius,
float borderThickness, float blur) {
float outerRoundBox = sdRoundedBox(p-origin, size, cornerRadius);
float innerRoundBox = sdRoundedBox(p-origin, size - vec2(borderThickness),
cornerRadius - borderThickness);
float d = max(outerRoundBox, -innerRoundBox);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
"""
}
}

View File

@@ -35,12 +35,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.reset
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -63,6 +63,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
controller = WiredChargingRippleController(
commandRegistry, batteryController, configurationController,
featureFlags, context, windowManager, systemClock, uiEventLogger)
rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2022 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.ripple
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
class RippleViewTest : SysuiTestCase() {
@Mock
private lateinit var rippleView: RippleView
@Before
fun setup() {
rippleView = RippleView(context, null)
}
@Test
fun testSetupShader_compilesCircle() {
rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
}
@Test
fun testSetupShader_compilesRoundedBox() {
rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
}
}