Merge "Make RippleShader support circle and rounded box shapes." into tm-qpr-dev

This commit is contained in:
Yein Jo
2022-08-04 03:56:16 +00:00
committed by Android (Google) Code Review
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
@@ -202,10 +201,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)
}
}