Merge "Animate LockScreen and AOD clock text for charging events" into sc-dev

This commit is contained in:
Shan Huang
2021-04-12 15:26:13 +00:00
committed by Android (Google) Code Review
8 changed files with 128 additions and 13 deletions

View File

@@ -42,6 +42,7 @@
android:typeface="monospace"
android:elegantTextHeight="false"
android:singleLine="true"
chargeAnimationDelay="350"
dozeWeight="200"
lockScreenWeight="400"
/>
@@ -62,6 +63,7 @@
android:fontFamily="@font/clock"
android:typeface="monospace"
android:elegantTextHeight="false"
chargeAnimationDelay="200"
dozeWeight="200"
lockScreenWeight="400"
/>

View File

@@ -45,5 +45,6 @@
<declare-styleable name="AnimatableClockView">
<attr name="dozeWeight" format="integer" />
<attr name="lockScreenWeight" format="integer" />
<attr name="chargeAnimationDelay" format="integer" />
</declare-styleable>
</resources>

View File

@@ -27,6 +27,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import java.util.Locale;
@@ -45,6 +46,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
private int mLockScreenColor;
private boolean mIsDozing;
private boolean mIsCharging;
private float mDozeAmount;
private Locale mLocale;
@@ -56,7 +58,8 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
public AnimatableClockController(
AnimatableClockView view,
StatusBarStateController statusBarStateController,
BroadcastDispatcher broadcastDispatcher) {
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController) {
super(view);
mStatusBarStateController = statusBarStateController;
mIsDozing = mStatusBarStateController.isDozing();
@@ -68,6 +71,16 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
R.dimen.keyguard_clock_line_spacing_scale_burmese);
mDefaultLineSpacing = getContext().getResources().getFloat(
R.dimen.keyguard_clock_line_spacing_scale);
batteryController.addCallback(new BatteryController.BatteryStateChangeCallback() {
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
if (!mIsCharging && charging) {
mView.animateCharge(mIsDozing);
}
mIsCharging = charging;
}
});
}
private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {

View File

@@ -42,7 +42,9 @@ public class AnimatableClockView extends TextView {
private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
private static final CharSequence SINGLE_LINE_FORMAT_12_HOUR = "h:mm";
private static final CharSequence SINGLE_LINE_FORMAT_24_HOUR = "H:mm";
private static final long ANIM_DURATION = 300;
private static final long DOZE_ANIM_DURATION = 300;
private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
private final Calendar mTime = Calendar.getInstance();
@@ -53,6 +55,7 @@ public class AnimatableClockView extends TextView {
private int mDozingColor;
private int mLockScreenColor;
private float mLineSpacingScale = 1f;
private int mChargeAnimationDelay = 0;
private TextAnimator mTextAnimator = null;
private Runnable mOnTextAnimatorInitialized;
@@ -79,6 +82,8 @@ public class AnimatableClockView extends TextView {
try {
mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
mChargeAnimationDelay = ta.getInt(
R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
} finally {
ta.recycle();
}
@@ -150,11 +155,36 @@ public class AnimatableClockView extends TextView {
mLockScreenColor = lockScreenColor;
}
void animateCharge(boolean isDozing) {
if (mTextAnimator == null || mTextAnimator.isRunning()) {
// Skip charge animation if dozing animation is already playing.
return;
}
Runnable startAnimPhase2 = () -> setTextStyle(
isDozing ? mDozingWeight : mLockScreenWeight/* weight */,
-1,
null,
true /* animate */,
CHARGE_ANIM_DURATION_PHASE_1,
0 /* delay */,
null /* onAnimationEnd */);
setTextStyle(isDozing ? mLockScreenWeight : mDozingWeight/* weight */,
-1,
null,
true /* animate */,
CHARGE_ANIM_DURATION_PHASE_0,
mChargeAnimationDelay,
startAnimPhase2);
}
void animateDoze(boolean isDozing, boolean animate) {
setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
-1,
isDozing ? mDozingColor : mLockScreenColor,
animate);
animate,
DOZE_ANIM_DURATION,
0 /* delay */,
null /* onAnimationEnd */);
}
/**
@@ -170,15 +200,20 @@ public class AnimatableClockView extends TextView {
private void setTextStyle(
@IntRange(from = 0, to = 1000) int weight,
@FloatRange(from = 0) float textSize,
int color,
boolean animate) {
Integer color,
boolean animate,
long duration,
long delay,
Runnable onAnimationEnd) {
if (mTextAnimator != null) {
mTextAnimator.setTextStyle(weight, textSize, color, animate, ANIM_DURATION, null);
mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
delay, onAnimationEnd);
} else {
// when the text animator is set, update its start values
mOnTextAnimatorInitialized =
() -> mTextAnimator.setTextStyle(
weight, textSize, color, false, ANIM_DURATION, null);
weight, textSize, color, false, duration, null,
delay, onAnimationEnd);
}
}

View File

@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import java.util.Locale;
@@ -70,6 +71,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BatteryController mBatteryController;
/**
* Clock for both small and large sizes
@@ -118,7 +120,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
BroadcastDispatcher broadcastDispatcher,
PluginManager pluginManager,
FeatureFlags featureFlags,
@Main Executor uiExecutor) {
@Main Executor uiExecutor,
BatteryController batteryController) {
super(keyguardClockSwitch);
mResources = resources;
mStatusBarStateController = statusBarStateController;
@@ -130,6 +133,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mPluginManager = pluginManager;
mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
mUiExecutor = uiExecutor;
mBatteryController = batteryController;
}
/**
@@ -156,14 +160,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view),
mStatusBarStateController,
mBroadcastDispatcher);
mBroadcastDispatcher,
mBatteryController);
mClockViewController.init();
mLargeClockViewController =
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view_large),
mStatusBarStateController,
mBroadcastDispatcher);
mBroadcastDispatcher,
mBatteryController);
mLargeClockViewController.init();
// If a smartspace plugin is detected, replace the existing smartspace

View File

@@ -65,7 +65,9 @@ class TextAnimator(
invalidateCallback()
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
override fun onAnimationEnd(animation: Animator?) {
textInterpolator.rebase()
}
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
})
}
@@ -74,6 +76,10 @@ class TextAnimator(
textInterpolator.layout = layout
}
fun isRunning(): Boolean {
return animator.isRunning
}
fun draw(c: Canvas) = textInterpolator.draw(c)
/**
@@ -101,7 +107,9 @@ class TextAnimator(
color: Int? = null,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
) {
if (animate) {
animator.cancel()
@@ -120,12 +128,25 @@ class TextAnimator(
textInterpolator.onTargetPaintModified()
if (animate) {
animator.startDelay = delay
animator.duration = if (duration == -1L) {
DEFAULT_ANIMATION_DURATION
} else {
duration
}
interpolator?.let { animator.interpolator = it }
if (onAnimationEnd != null) {
val listener = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd.run()
animator.removeListener(this)
}
override fun onAnimationCancel(animation: Animator?) {
animator.removeListener(this)
}
}
animator.addListener(listener)
}
animator.start()
} else {
// No animation is requested, thus set base and target state to the same state.

View File

@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import org.junit.Before;
import org.junit.Test;
@@ -96,6 +97,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private AnimatableClockView mClockView;
@Mock
private AnimatableClockView mLargeClockView;
@Mock
BatteryController mBatteryController;
private KeyguardClockSwitchController mController;
@@ -127,7 +130,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mBroadcastDispatcher,
mPluginManager,
mFeatureFlags,
mExecutor);
mExecutor,
mBatteryController);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);

View File

@@ -16,6 +16,7 @@
package com.android.keyguard
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner
import android.text.Layout
@@ -25,7 +26,9 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -107,4 +110,34 @@ class TextAnimatorTest : SysuiTestCase() {
// Then, animation start should not be called.
verify(valueAnimator, never()).start()
}
@Test
fun testAnimationEnded() {
val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
val animationEndCallback = mock(Runnable::class.java)
val textAnimator = TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(
weight = 400,
animate = true,
onAnimationEnd = animationEndCallback
)
// Verify animationEnd callback has been added.
val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java)
verify(valueAnimator).addListener(captor.capture())
captor.value.onAnimationEnd(valueAnimator)
// Verify animationEnd callback has been invoked and removed.
verify(animationEndCallback).run()
verify(valueAnimator).removeListener(eq(captor.value))
}
}