Merge "Add charging ripple to wireless charging animation." into sc-dev

This commit is contained in:
Shan Huang
2021-03-25 09:46:58 +00:00
committed by Android (Google) Code Review
8 changed files with 76 additions and 63 deletions

View File

@@ -22,6 +22,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.android.systemui.statusbar.charging.ChargingRippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- Circle animation --> <!-- Circle animation -->
<ImageView <ImageView
android:id="@+id/wireless_charging_view" android:id="@+id/wireless_charging_view"

View File

@@ -26,7 +26,6 @@ import android.os.Message;
import android.util.Log; import android.util.Log;
import android.util.Slog; import android.util.Slog;
import android.view.Gravity; import android.view.Gravity;
import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
/** /**
@@ -98,8 +97,8 @@ public class WirelessChargingAnimation {
private final Handler mHandler; private final Handler mHandler;
private int mGravity; private int mGravity;
private View mView; private WirelessChargingLayout mView;
private View mNextView; private WirelessChargingLayout mNextView;
private WindowManager mWM; private WindowManager mWM;
private Callback mCallback; private Callback mCallback;
@@ -112,7 +111,7 @@ public class WirelessChargingAnimation {
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
final WindowManager.LayoutParams params = mParams; final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.width = WindowManager.LayoutParams.MATCH_PARENT; params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.format = PixelFormat.TRANSLUCENT; params.format = PixelFormat.TRANSLUCENT;

View File

@@ -20,6 +20,7 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.TypedValue; import android.util.TypedValue;
@@ -29,8 +30,10 @@ import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators; import com.android.systemui.Interpolators;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.statusbar.charging.ChargingRippleView;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -38,7 +41,9 @@ import java.text.NumberFormat;
* @hide * @hide
*/ */
public class WirelessChargingLayout extends FrameLayout { public class WirelessChargingLayout extends FrameLayout {
public final static int UNKNOWN_BATTERY_LEVEL = -1; public static final int UNKNOWN_BATTERY_LEVEL = -1;
private static final long RIPPLE_ANIMATION_DURATION = 2000;
private ChargingRippleView mRippleView;
public WirelessChargingLayout(Context context) { public WirelessChargingLayout(Context context) {
super(context); super(context);
@@ -120,6 +125,8 @@ public class WirelessChargingLayout extends FrameLayout {
AnimatorSet animatorSet = new AnimatorSet(); AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
mRippleView = findViewById(R.id.wireless_charging_ripple);
if (!showTransmittingBatteryLevel) { if (!showTransmittingBatteryLevel) {
chargingAnimation.start(); chargingAnimation.start();
animatorSet.start(); animatorSet.start();
@@ -195,4 +202,21 @@ public class WirelessChargingLayout extends FrameLayout {
animatorSetTransmitting.start(); animatorSetTransmitting.start();
animatorSetIcon.start(); animatorSetIcon.start();
} }
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
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.setDuration(RIPPLE_ANIMATION_DURATION);
mRippleView.startRipple();
}
super.onLayout(changed, left, top, right, bottom);
}
} }

View File

@@ -25,10 +25,8 @@ import android.graphics.Paint
import android.graphics.PointF import android.graphics.PointF
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import kotlin.math.max
private const val RIPPLE_ANIMATION_DURATION: Long = 1500 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
/** /**
* Expanding ripple effect that shows when charging begins. * Expanding ripple effect that shows when charging begins.
@@ -39,17 +37,18 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
private val defaultColor: Int = 0xffffffff.toInt() private val defaultColor: Int = 0xffffffff.toInt()
private val ripplePaint = Paint() private val ripplePaint = Paint()
var radius: Float = 0.0f
set(value) { rippleShader.radius = value }
var origin: PointF = PointF()
set(value) { rippleShader.origin = value }
var duration: Long = 1500
init { init {
rippleShader.color = defaultColor rippleShader.color = defaultColor
rippleShader.progress = 0f rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader ripplePaint.shader = rippleShader
} visibility = View.GONE
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
rippleShader.origin = PointF(measuredWidth / 2f, measuredHeight.toFloat())
rippleShader.radius = max(measuredWidth, measuredHeight).toFloat()
super.onLayout(changed, left, top, right, bottom)
} }
fun startRipple() { fun startRipple() {
@@ -57,7 +56,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
return // Ignore if ripple effect is already playing return // Ignore if ripple effect is already playing
} }
val animator = ValueAnimator.ofFloat(0f, 1f) val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = RIPPLE_ANIMATION_DURATION animator.duration = duration
animator.addUpdateListener { animator -> animator.addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
val phase = now / 30000f val phase = now / 30000f

View File

@@ -53,7 +53,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
float s = 0.0; float s = 0.0;
for (float i = 0; i < 4; i += 1) { for (float i = 0; i < 4; i += 1) {
float l = i * 0.25; float l = i * 0.25;
float h = l + 0.025; float h = l + 0.005;
float o = abs(sin(0.1 * PI * (t + i))); float o = abs(sin(0.1 * PI * (t + i)));
s += threshold(n + o, l, h); s += threshold(n + o, l, h);
} }
@@ -97,7 +97,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
float fadeRipple = min(fadeIn, 1.-fadeOutRipple); float fadeRipple = min(fadeIn, 1.-fadeOutRipple);
float rippleAlpha = softRing(p, in_origin, radius, 0.5) float rippleAlpha = softRing(p, in_origin, radius, 0.5)
* fadeRipple * in_color.a; * fadeRipple * in_color.a;
vec4 ripple = in_color * max(circle, rippleAlpha) * 0.4; vec4 ripple = in_color * max(circle, rippleAlpha) * 0.3;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}""" }"""
private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.charging
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.PointF
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.View import android.view.View
import android.view.ViewGroupOverlay import android.view.ViewGroupOverlay
@@ -31,6 +32,7 @@ import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.Integer.max
import javax.inject.Inject import javax.inject.Inject
/*** /***
@@ -46,7 +48,7 @@ class WiredChargingRippleController @Inject constructor(
private val context: Context, private val context: Context,
private val keyguardStateController: KeyguardStateController private val keyguardStateController: KeyguardStateController
) { ) {
private var pluggedIn: Boolean? = null private var charging: Boolean? = null
private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled
@VisibleForTesting @VisibleForTesting
var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null) var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
@@ -55,16 +57,18 @@ class WiredChargingRippleController @Inject constructor(
val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback { val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged( override fun onBatteryLevelChanged(
level: Int, level: Int,
nowPluggedIn: Boolean, pluggedIn: Boolean,
charging: Boolean nowCharging: Boolean
) { ) {
if (!rippleEnabled) { // Suppresses the ripple when it's disabled, or when the state change comes
// from wireless charging.
if (!rippleEnabled || batteryController.isWirelessCharging) {
return return
} }
val wasPluggedIn = pluggedIn val wasCharging = charging
pluggedIn = nowPluggedIn charging = nowCharging
// Only triggers when the keyguard is active and the device is just plugged in. // Only triggers when the keyguard is active and the device is just plugged in.
if (wasPluggedIn == false && nowPluggedIn && keyguardStateController.isShowing) { if (wasCharging == false && nowCharging && keyguardStateController.isShowing) {
rippleView.startRipple() rippleView.startRipple()
} }
} }
@@ -113,10 +117,13 @@ class WiredChargingRippleController @Inject constructor(
val width = displayMetrics.widthPixels val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels val height = displayMetrics.heightPixels
if (width != rippleView.width || height != rippleView.height) { if (width != rippleView.width || height != rippleView.height) {
rippleView.measure( rippleView.apply {
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)) View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
rippleView.layout(0, 0, width, height) layout(0, 0, width, height)
origin = PointF(width / 2f, height.toFloat())
radius = max(width, height).toFloat()
}
} }
} }

View File

@@ -184,7 +184,6 @@ import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal; import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyboardShortcuts;
@@ -2469,39 +2468,19 @@ public class StatusBar extends SystemUI implements DemoMode,
protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel, protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,
long animationDelay) { long animationDelay) {
if (mDozing || mKeyguardManager.isKeyguardLocked()) { WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
// on ambient or lockscreen, hide notification panel transmittingBatteryLevel, batteryLevel,
WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, new WirelessChargingAnimation.Callback() {
transmittingBatteryLevel, batteryLevel, @Override
new WirelessChargingAnimation.Callback() { public void onAnimationStarting() {
@Override mNotificationShadeWindowController.setRequestTopUi(true, TAG);
public void onAnimationStarting() { }
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
}
@Override @Override
public void onAnimationEnded() { public void onAnimationEnded() {
CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView()); mNotificationShadeWindowController.setRequestTopUi(false, TAG);
mNotificationShadeWindowController.setRequestTopUi(false, TAG); }
} }, false).show(animationDelay);
}, mDozing).show(animationDelay);
} else {
// workspace
WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
transmittingBatteryLevel, batteryLevel,
new WirelessChargingAnimation.Callback() {
@Override
public void onAnimationStarting() {
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
}
@Override
public void onAnimationEnded() {
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
}
}, false).show(animationDelay);
}
} }
@Override @Override

View File

@@ -93,8 +93,8 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
captor.value.onBatteryLevelChanged( captor.value.onBatteryLevelChanged(
unusedBatteryLevel, unusedBatteryLevel,
true /* plugged in */, false /* plugged in */,
false /* charging */) true /* charging */)
verify(rippleView).startRipple() verify(rippleView).startRipple()
} }