Merge "Animate LockScreen and AOD clock text for charging events" into sc-dev
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user