Merge "Add charging ripple to wireless charging animation." into sc-dev
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user