Add large screen clock motion - method 2.
When the clock moves on a large screen (e.g. when locking/unlocking the device), this adds a subtle offset for each digit of the clock. This hooks a new transition in to the existing clock move transition, which just forwards progress through to the actual clock. This also adds a feature flag (210) to enable/disable this, since it needs a little more polish yet. As above, this needs more polish (tweaking the time and/or easing), but this at least gets something moving to start with :). Bug: 199051139 Test: Manually verified. Change-Id: Icec0bcfdb431d24b9405c5dfe8993016f687cac5
This commit is contained in:
@@ -82,6 +82,14 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
|||||||
/** Mutable Y coordinate of the glyph position relative from the baseline. */
|
/** Mutable Y coordinate of the glyph position relative from the baseline. */
|
||||||
var y: Float = 0f
|
var y: Float = 0f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current line of text being drawn, in a multi-line TextView.
|
||||||
|
*/
|
||||||
|
var lineNo: Int = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable text size of the glyph in pixels.
|
||||||
|
*/
|
||||||
/** Mutable text size of the glyph in pixels. */
|
/** Mutable text size of the glyph in pixels. */
|
||||||
var textSize: Float = 0f
|
var textSize: Float = 0f
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,9 @@ class TextInterpolator(layout: Layout) {
|
|||||||
val origin = layout.getDrawOrigin(lineNo)
|
val origin = layout.getDrawOrigin(lineNo)
|
||||||
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
|
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
|
||||||
|
|
||||||
run.fontRuns.forEach { fontRun -> drawFontRun(canvas, run, fontRun, tmpPaint) }
|
run.fontRuns.forEach { fontRun ->
|
||||||
|
drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
canvas.restore()
|
canvas.restore()
|
||||||
}
|
}
|
||||||
@@ -341,7 +343,7 @@ class TextInterpolator(layout: Layout) {
|
|||||||
var glyphFilter: GlyphCallback? = null
|
var glyphFilter: GlyphCallback? = null
|
||||||
|
|
||||||
// Draws single font run.
|
// Draws single font run.
|
||||||
private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
|
private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) {
|
||||||
var arrayIndex = 0
|
var arrayIndex = 0
|
||||||
val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
|
val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
|
||||||
|
|
||||||
@@ -360,11 +362,13 @@ class TextInterpolator(layout: Layout) {
|
|||||||
tmpGlyph.font = font
|
tmpGlyph.font = font
|
||||||
tmpGlyph.runStart = run.start
|
tmpGlyph.runStart = run.start
|
||||||
tmpGlyph.runLength = run.end - run.start
|
tmpGlyph.runLength = run.end - run.start
|
||||||
|
tmpGlyph.lineNo = lineNo
|
||||||
|
|
||||||
tmpPaintForGlyph.set(paint)
|
tmpPaintForGlyph.set(paint)
|
||||||
var prevStart = run.start
|
var prevStart = run.start
|
||||||
|
|
||||||
for (i in run.start until run.end) {
|
for (i in run.start until run.end) {
|
||||||
|
tmpGlyph.glyphIndex = i
|
||||||
tmpGlyph.glyphId = line.glyphIds[i]
|
tmpGlyph.glyphId = line.glyphIds[i]
|
||||||
tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
|
tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
|
||||||
tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
|
tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
package com.android.systemui.plugins
|
package com.android.systemui.plugins
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.android.systemui.plugins.annotations.ProvidesInterface
|
import com.android.systemui.plugins.annotations.ProvidesInterface
|
||||||
@@ -114,6 +115,17 @@ interface ClockAnimations {
|
|||||||
|
|
||||||
/** Runs the battery animation (if any). */
|
/** Runs the battery animation (if any). */
|
||||||
fun charge() { }
|
fun charge() { }
|
||||||
|
|
||||||
|
/** Move the clock, for example, if the notification tray appears in split-shade mode. */
|
||||||
|
fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this clock has a custom position update animation. If true, the keyguard will call
|
||||||
|
* `onPositionUpdated` to notify the clock of a position update animation. If false, a default
|
||||||
|
* animation will be used (e.g. a simple translation).
|
||||||
|
*/
|
||||||
|
val hasCustomPositionUpdatedAnimation
|
||||||
|
get() = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Events that have specific data about the related face */
|
/** Events that have specific data about the related face */
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
|
android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
|
||||||
|
android:clipChildren="false"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- Not quite optimal but needed to translate these items as a group. The
|
<!-- Not quite optimal but needed to translate these items as a group. The
|
||||||
|
|||||||
@@ -20,16 +20,15 @@ import android.annotation.ColorInt
|
|||||||
import android.annotation.FloatRange
|
import android.annotation.FloatRange
|
||||||
import android.annotation.IntRange
|
import android.annotation.IntRange
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.compat.ChangeIdStateCache.invalidate
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
import android.text.Layout
|
import android.text.Layout
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.MathUtils
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.android.internal.R.attr.contentDescription
|
|
||||||
import com.android.internal.R.attr.format
|
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.animation.GlyphCallback
|
import com.android.systemui.animation.GlyphCallback
|
||||||
import com.android.systemui.animation.Interpolators
|
import com.android.systemui.animation.Interpolators
|
||||||
@@ -39,6 +38,8 @@ import java.io.PrintWriter
|
|||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
||||||
@@ -311,7 +312,24 @@ class AnimatableClockView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
|
// The offset of each glyph from where it should be.
|
||||||
|
private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
|
||||||
|
|
||||||
|
private var lastSeenAnimationProgress = 1.0f
|
||||||
|
|
||||||
|
// If the animation is being reversed, the target offset for each glyph for the "stop".
|
||||||
|
private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
|
||||||
|
private var animationCancelStopPosition = 0.0f
|
||||||
|
|
||||||
|
// Whether the currently playing animation needed a stop (and thus, is shortened).
|
||||||
|
private var currentAnimationNeededStop = false
|
||||||
|
|
||||||
|
private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
|
||||||
|
val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
|
||||||
|
if (offset < glyphOffsets.size) {
|
||||||
|
positionedGlyph.x += glyphOffsets[offset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set text style with an optional animation.
|
* Set text style with an optional animation.
|
||||||
@@ -421,6 +439,124 @@ class AnimatableClockView @JvmOverloads constructor(
|
|||||||
pw.println(" time=$time")
|
pw.println(" time=$time")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
|
||||||
|
// Do we need to cancel an in-flight animation?
|
||||||
|
// Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
|
||||||
|
// which trips up the check otherwise.
|
||||||
|
if (lastSeenAnimationProgress != 1.0f &&
|
||||||
|
lastSeenAnimationProgress != 0.0f &&
|
||||||
|
fraction == 0.0f) {
|
||||||
|
// New animation, but need to stop the old one. Figure out where each glyph currently
|
||||||
|
// is in relation to the box position. After that, use the leading digit's current
|
||||||
|
// position as the stop target.
|
||||||
|
currentAnimationNeededStop = true
|
||||||
|
|
||||||
|
// We assume that the current glyph offsets would be relative to the "from" position.
|
||||||
|
val moveAmount = toRect.left - fromRect.left
|
||||||
|
|
||||||
|
// Remap the current glyph offsets to be relative to the new "end" position, and figure
|
||||||
|
// out the start/end positions for the stop animation.
|
||||||
|
for (i in 0 until NUM_DIGITS) {
|
||||||
|
glyphOffsets[i] = -moveAmount + glyphOffsets[i]
|
||||||
|
animationCancelStartPosition[i] = glyphOffsets[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the leading digit's offset as the stop position.
|
||||||
|
if (toRect.left > fromRect.left) {
|
||||||
|
// It _was_ moving left
|
||||||
|
animationCancelStopPosition = glyphOffsets[0]
|
||||||
|
} else {
|
||||||
|
// It was moving right
|
||||||
|
animationCancelStopPosition = glyphOffsets[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is there a cancellation in progress?
|
||||||
|
if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
|
||||||
|
val animationStopProgress = MathUtils.constrainedMap(
|
||||||
|
0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
|
||||||
|
)
|
||||||
|
|
||||||
|
// One of the digits has already stopped.
|
||||||
|
val animationStopStep = 1.0f / (NUM_DIGITS - 1)
|
||||||
|
|
||||||
|
for (i in 0 until NUM_DIGITS) {
|
||||||
|
val stopAmount = if (toRect.left > fromRect.left) {
|
||||||
|
// It was moving left (before flipping)
|
||||||
|
MOVE_LEFT_DELAYS[i] * animationStopStep
|
||||||
|
} else {
|
||||||
|
// It was moving right (before flipping)
|
||||||
|
MOVE_RIGHT_DELAYS[i] * animationStopStep
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leading digit stops immediately.
|
||||||
|
if (stopAmount == 0.0f) {
|
||||||
|
glyphOffsets[i] = animationCancelStopPosition
|
||||||
|
} else {
|
||||||
|
val actualStopAmount = MathUtils.constrainedMap(
|
||||||
|
0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
|
||||||
|
)
|
||||||
|
val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
|
||||||
|
val glyphMoveAmount =
|
||||||
|
animationCancelStopPosition - animationCancelStartPosition[i]
|
||||||
|
glyphOffsets[i] =
|
||||||
|
animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal part of the animation.
|
||||||
|
// Do we need to remap the animation progress to take account of the cancellation?
|
||||||
|
val actualFraction = if (currentAnimationNeededStop) {
|
||||||
|
MathUtils.constrainedMap(
|
||||||
|
0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fraction
|
||||||
|
}
|
||||||
|
|
||||||
|
val digitFractions = (0 until NUM_DIGITS).map {
|
||||||
|
// The delay for each digit, in terms of fraction (i.e. the digit should not move
|
||||||
|
// during 0.0 - 0.1).
|
||||||
|
val initialDelay = if (toRect.left > fromRect.left) {
|
||||||
|
MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
|
||||||
|
} else {
|
||||||
|
MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
|
||||||
|
}
|
||||||
|
|
||||||
|
val f = MathUtils.constrainedMap(
|
||||||
|
0.0f, 1.0f,
|
||||||
|
initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
|
||||||
|
actualFraction
|
||||||
|
)
|
||||||
|
MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was there an animation halt?
|
||||||
|
val moveAmount = if (currentAnimationNeededStop) {
|
||||||
|
// Only need to animate over the remaining space if the animation was aborted.
|
||||||
|
-animationCancelStopPosition
|
||||||
|
} else {
|
||||||
|
toRect.left.toFloat() - fromRect.left.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until NUM_DIGITS) {
|
||||||
|
glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate()
|
||||||
|
|
||||||
|
if (fraction == 1.0f) {
|
||||||
|
// Reset
|
||||||
|
currentAnimationNeededStop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSeenAnimationProgress = fraction
|
||||||
|
|
||||||
|
// Ensure that the actual clock container is always in the "end" position.
|
||||||
|
this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
|
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
|
||||||
// This is an optimization to ensure we only recompute the patterns when the inputs change.
|
// This is an optimization to ensure we only recompute the patterns when the inputs change.
|
||||||
private object Patterns {
|
private object Patterns {
|
||||||
@@ -458,5 +594,36 @@ class AnimatableClockView @JvmOverloads constructor(
|
|||||||
private const val APPEAR_ANIM_DURATION: Long = 350
|
private const val APPEAR_ANIM_DURATION: Long = 350
|
||||||
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
|
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
|
||||||
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
|
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
|
||||||
|
|
||||||
|
// Constants for the animation
|
||||||
|
private val MOVE_INTERPOLATOR = Interpolators.STANDARD
|
||||||
|
|
||||||
|
// Calculate the positions of all of the digits...
|
||||||
|
// Offset each digit by, say, 0.1
|
||||||
|
// This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
|
||||||
|
// move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
|
||||||
|
// from 0.3 - 1.0.
|
||||||
|
private const val NUM_DIGITS = 4
|
||||||
|
private const val DIGITS_PER_LINE = 2
|
||||||
|
|
||||||
|
// How much of "fraction" to spend on canceling the animation, if needed
|
||||||
|
private const val ANIMATION_CANCELLATION_TIME = 0.4f
|
||||||
|
|
||||||
|
// Delays. Each digit's animation should have a slight delay, so we get a nice
|
||||||
|
// "stepping" effect. When moving right, the second digit of the hour should move first.
|
||||||
|
// When moving left, the first digit of the hour should move first. The lists encode
|
||||||
|
// the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
|
||||||
|
// by delayMultiplier.
|
||||||
|
private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
|
||||||
|
private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
|
||||||
|
|
||||||
|
// How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
|
||||||
|
// (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
|
||||||
|
// before moving).
|
||||||
|
private const val MOVE_DIGIT_STEP = 0.1f
|
||||||
|
|
||||||
|
// Total available transition time for each digit, taking into account the step. If step is
|
||||||
|
// 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
|
||||||
|
private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Rect
|
||||||
import android.icu.text.NumberFormat
|
import android.icu.text.NumberFormat
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -130,6 +131,10 @@ class DefaultClockController(
|
|||||||
lp.topMargin = (-0.5f * view.bottom).toInt()
|
lp.topMargin = (-0.5f * view.bottom).toInt()
|
||||||
view.setLayoutParams(lp)
|
view.setLayoutParams(lp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
|
||||||
|
view.moveForSplitShade(fromRect, toRect, fraction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class DefaultClockEvents : ClockEvents {
|
inner class DefaultClockEvents : ClockEvents {
|
||||||
@@ -209,6 +214,13 @@ class DefaultClockController(
|
|||||||
clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
|
clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
|
||||||
|
largeClock.moveForSplitShade(fromRect, toRect, fraction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val hasCustomPositionUpdatedAnimation: Boolean
|
||||||
|
get() = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AnimationState(
|
private class AnimationState(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.android.systemui.dump.DumpManager;
|
|||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.flags.Flags;
|
import com.android.systemui.flags.Flags;
|
||||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||||
|
import com.android.systemui.plugins.ClockAnimations;
|
||||||
import com.android.systemui.plugins.ClockController;
|
import com.android.systemui.plugins.ClockController;
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||||
import com.android.systemui.shared.clocks.ClockRegistry;
|
import com.android.systemui.shared.clocks.ClockRegistry;
|
||||||
@@ -404,5 +405,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
|||||||
clock.dump(pw);
|
clock.dump(pw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/** Gets the animations for the current clock. */
|
||||||
|
public ClockAnimations getClockAnimations() {
|
||||||
|
return getClock().getAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.graphics.Rect;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
|
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
|
||||||
|
import com.android.systemui.plugins.ClockAnimations;
|
||||||
import com.android.systemui.statusbar.notification.AnimatableProperty;
|
import com.android.systemui.statusbar.notification.AnimatableProperty;
|
||||||
import com.android.systemui.statusbar.notification.PropertyAnimator;
|
import com.android.systemui.statusbar.notification.PropertyAnimator;
|
||||||
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
|
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
|
||||||
@@ -232,4 +233,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
|
|||||||
mView.setClipBounds(null);
|
mView.setClipBounds(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the animations for the current clock. */
|
||||||
|
public ClockAnimations getClockAnimations() {
|
||||||
|
return mKeyguardClockSwitchController.getClockAnimations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ public class Flags {
|
|||||||
*/
|
*/
|
||||||
public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
|
public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
|
||||||
|
* the digits when the clock moves.
|
||||||
|
*/
|
||||||
|
public static final UnreleasedFlag STEP_CLOCK_ANIMATION = new UnreleasedFlag(211);
|
||||||
|
|
||||||
/***************************************/
|
/***************************************/
|
||||||
// 300 - power menu
|
// 300 - power menu
|
||||||
public static final ReleasedFlag POWER_MENU_LITE =
|
public static final ReleasedFlag POWER_MENU_LITE =
|
||||||
|
|||||||
@@ -79,7 +79,10 @@ import android.os.UserManager;
|
|||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.transition.ChangeBounds;
|
import android.transition.ChangeBounds;
|
||||||
|
import android.transition.Transition;
|
||||||
import android.transition.TransitionManager;
|
import android.transition.TransitionManager;
|
||||||
|
import android.transition.TransitionSet;
|
||||||
|
import android.transition.TransitionValues;
|
||||||
import android.util.IndentingPrintWriter;
|
import android.util.IndentingPrintWriter;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.MathUtils;
|
import android.util.MathUtils;
|
||||||
@@ -1664,9 +1667,40 @@ public final class NotificationPanelViewController {
|
|||||||
// horizontally properly.
|
// horizontally properly.
|
||||||
transition.excludeTarget(R.id.status_view_media_container, true);
|
transition.excludeTarget(R.id.status_view_media_container, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||||
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||||
TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
|
|
||||||
|
boolean customClockAnimation =
|
||||||
|
mKeyguardStatusViewController
|
||||||
|
.getClockAnimations()
|
||||||
|
.getHasCustomPositionUpdatedAnimation();
|
||||||
|
|
||||||
|
if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
|
||||||
|
// Find the clock, so we can exclude it from this transition.
|
||||||
|
FrameLayout clockContainerView =
|
||||||
|
mView.findViewById(R.id.lockscreen_clock_view_large);
|
||||||
|
View clockView = clockContainerView.getChildAt(0);
|
||||||
|
|
||||||
|
transition.excludeTarget(clockView, /* exclude= */ true);
|
||||||
|
|
||||||
|
TransitionSet set = new TransitionSet();
|
||||||
|
set.addTransition(transition);
|
||||||
|
|
||||||
|
SplitShadeTransitionAdapter adapter =
|
||||||
|
new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
|
||||||
|
|
||||||
|
// Use linear here, so the actual clock can pick its own interpolator.
|
||||||
|
adapter.setInterpolator(Interpolators.LINEAR);
|
||||||
|
adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||||
|
adapter.addTarget(clockView);
|
||||||
|
set.addTransition(adapter);
|
||||||
|
|
||||||
|
TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
|
||||||
|
} else {
|
||||||
|
TransitionManager.beginDelayedTransition(
|
||||||
|
mNotificationContainerParent, transition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constraintSet.applyTo(mNotificationContainerParent);
|
constraintSet.applyTo(mNotificationContainerParent);
|
||||||
@@ -6238,4 +6272,54 @@ public final class NotificationPanelViewController {
|
|||||||
loadDimens();
|
loadDimens();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class SplitShadeTransitionAdapter extends Transition {
|
||||||
|
private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
|
||||||
|
private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
|
||||||
|
|
||||||
|
private final KeyguardStatusViewController mController;
|
||||||
|
|
||||||
|
SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
|
||||||
|
mController = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void captureValues(TransitionValues transitionValues) {
|
||||||
|
Rect boundsRect = new Rect();
|
||||||
|
boundsRect.left = transitionValues.view.getLeft();
|
||||||
|
boundsRect.top = transitionValues.view.getTop();
|
||||||
|
boundsRect.right = transitionValues.view.getRight();
|
||||||
|
boundsRect.bottom = transitionValues.view.getBottom();
|
||||||
|
transitionValues.values.put(PROP_BOUNDS, boundsRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void captureEndValues(TransitionValues transitionValues) {
|
||||||
|
captureValues(transitionValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void captureStartValues(TransitionValues transitionValues) {
|
||||||
|
captureValues(transitionValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
|
||||||
|
TransitionValues endValues) {
|
||||||
|
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||||
|
|
||||||
|
Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
|
||||||
|
Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
|
||||||
|
|
||||||
|
anim.addUpdateListener(
|
||||||
|
animation -> mController.getClockAnimations().onPositionUpdated(
|
||||||
|
from, to, animation.getAnimatedFraction()));
|
||||||
|
|
||||||
|
return anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getTransitionProperties() {
|
||||||
|
return TRANSITION_PROPERTIES;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -45,6 +46,7 @@ import com.android.systemui.SysuiTestCase;
|
|||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||||
|
import com.android.systemui.plugins.ClockAnimations;
|
||||||
import com.android.systemui.plugins.ClockController;
|
import com.android.systemui.plugins.ClockController;
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||||
import com.android.systemui.shared.clocks.AnimatableClockView;
|
import com.android.systemui.shared.clocks.AnimatableClockView;
|
||||||
@@ -262,6 +264,19 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
|
|||||||
verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
|
verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetClockAnimationsForwardsToClock() {
|
||||||
|
ClockController mockClockController = mock(ClockController.class);
|
||||||
|
ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
|
||||||
|
when(mClockEventController.getClock()).thenReturn(mockClockController);
|
||||||
|
when(mockClockController.getAnimations()).thenReturn(mockClockAnimations);
|
||||||
|
|
||||||
|
Rect r1 = new Rect(1, 2, 3, 4);
|
||||||
|
Rect r2 = new Rect(5, 6, 7, 8);
|
||||||
|
mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f);
|
||||||
|
verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
private void verifyAttachment(VerificationMode times) {
|
private void verifyAttachment(VerificationMode times) {
|
||||||
verify(mClockRegistry, times).registerClockChangeListener(
|
verify(mClockRegistry, times).registerClockChangeListener(
|
||||||
any(ClockRegistry.ClockChangeListener.class));
|
any(ClockRegistry.ClockChangeListener.class));
|
||||||
|
|||||||
@@ -16,12 +16,16 @@
|
|||||||
|
|
||||||
package com.android.keyguard;
|
package com.android.keyguard;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
|
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.plugins.ClockAnimations;
|
||||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||||
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
|
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||||
@@ -108,4 +112,16 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
|
|||||||
configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
|
configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
|
||||||
verify(mKeyguardClockSwitchController).onLocaleListChanged();
|
verify(mKeyguardClockSwitchController).onLocaleListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getClockAnimations_forwardsToClockSwitch() {
|
||||||
|
ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
|
||||||
|
when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations);
|
||||||
|
|
||||||
|
Rect r1 = new Rect(1, 2, 3, 4);
|
||||||
|
Rect r2 = new Rect(5, 6, 7, 8);
|
||||||
|
mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f);
|
||||||
|
|
||||||
|
verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user