Merge "Add large screen clock motion - method 2." into tm-qpr-dev am: 63322e05fb
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19927145 Change-Id: I477455399426d817306704139c8228c41f7c4de0 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -88,6 +88,11 @@ class TextAnimator(
|
|||||||
*/
|
*/
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ class TextInterpolator(
|
|||||||
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
|
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
|
||||||
|
|
||||||
run.fontRuns.forEach { fontRun ->
|
run.fontRuns.forEach { fontRun ->
|
||||||
drawFontRun(canvas, run, fontRun, tmpPaint)
|
drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
canvas.restore()
|
canvas.restore()
|
||||||
@@ -349,7 +349,7 @@ class TextInterpolator(
|
|||||||
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)
|
||||||
|
|
||||||
@@ -368,11 +368,13 @@ class TextInterpolator(
|
|||||||
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)
|
||||||
@@ -316,7 +317,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.
|
||||||
@@ -432,6 +450,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 {
|
||||||
@@ -469,5 +605,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,10 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ public class Flags {
|
|||||||
// TODO(b/254513102): Tracking Bug
|
// TODO(b/254513102): Tracking Bug
|
||||||
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(212);
|
||||||
|
|
||||||
/***************************************/
|
/***************************************/
|
||||||
// 300 - power menu
|
// 300 - power menu
|
||||||
// TODO(b/254512600): Tracking Bug
|
// TODO(b/254512600): Tracking Bug
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1667,9 +1670,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() != null
|
||||||
|
&& 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);
|
||||||
@@ -4166,8 +4200,8 @@ public final class NotificationPanelViewController {
|
|||||||
/**
|
/**
|
||||||
* Sets the dozing state.
|
* Sets the dozing state.
|
||||||
*
|
*
|
||||||
* @param dozing {@code true} when dozing.
|
* @param dozing {@code true} when dozing.
|
||||||
* @param animate if transition should be animated.
|
* @param animate if transition should be animated.
|
||||||
*/
|
*/
|
||||||
public void setDozing(boolean dozing, boolean animate) {
|
public void setDozing(boolean dozing, boolean animate) {
|
||||||
if (dozing == mDozing) return;
|
if (dozing == mDozing) return;
|
||||||
@@ -4307,35 +4341,35 @@ public final class NotificationPanelViewController {
|
|||||||
/**
|
/**
|
||||||
* Starts fold to AOD animation.
|
* Starts fold to AOD animation.
|
||||||
*
|
*
|
||||||
* @param startAction invoked when the animation starts.
|
* @param startAction invoked when the animation starts.
|
||||||
* @param endAction invoked when the animation finishes, also if it was cancelled.
|
* @param endAction invoked when the animation finishes, also if it was cancelled.
|
||||||
* @param cancelAction invoked when the animation is cancelled, before endAction.
|
* @param cancelAction invoked when the animation is cancelled, before endAction.
|
||||||
*/
|
*/
|
||||||
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
|
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
|
||||||
Runnable cancelAction) {
|
Runnable cancelAction) {
|
||||||
mView.animate()
|
mView.animate()
|
||||||
.translationX(0)
|
.translationX(0)
|
||||||
.alpha(1f)
|
.alpha(1f)
|
||||||
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
|
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
|
||||||
.setInterpolator(EMPHASIZED_DECELERATE)
|
.setInterpolator(EMPHASIZED_DECELERATE)
|
||||||
.setListener(new AnimatorListenerAdapter() {
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationStart(Animator animation) {
|
public void onAnimationStart(Animator animation) {
|
||||||
startAction.run();
|
startAction.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationCancel(Animator animation) {
|
public void onAnimationCancel(Animator animation) {
|
||||||
cancelAction.run();
|
cancelAction.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
endAction.run();
|
endAction.run();
|
||||||
}
|
}
|
||||||
}).setUpdateListener(anim -> {
|
}).setUpdateListener(anim -> {
|
||||||
mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
|
mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4695,8 +4729,10 @@ public final class NotificationPanelViewController {
|
|||||||
/**
|
/**
|
||||||
* Maybe vibrate as panel is opened.
|
* Maybe vibrate as panel is opened.
|
||||||
*
|
*
|
||||||
* @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
|
* @param openingWithTouch Whether the panel is being opened with touch. If the panel is
|
||||||
* being opened programmatically (such as by the open panel gesture), we always play haptic.
|
* instead
|
||||||
|
* being opened programmatically (such as by the open panel gesture), we
|
||||||
|
* always play haptic.
|
||||||
*/
|
*/
|
||||||
private void maybeVibrateOnOpening(boolean openingWithTouch) {
|
private void maybeVibrateOnOpening(boolean openingWithTouch) {
|
||||||
if (mVibrateOnOpening) {
|
if (mVibrateOnOpening) {
|
||||||
@@ -4861,10 +4897,12 @@ public final class NotificationPanelViewController {
|
|||||||
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||||
animator.addListener(new AnimatorListenerAdapter() {
|
animator.addListener(new AnimatorListenerAdapter() {
|
||||||
private boolean mCancelled;
|
private boolean mCancelled;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationCancel(Animator animation) {
|
public void onAnimationCancel(Animator animation) {
|
||||||
mCancelled = true;
|
mCancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
mIsSpringBackAnimation = false;
|
mIsSpringBackAnimation = false;
|
||||||
@@ -4912,7 +4950,7 @@ public final class NotificationPanelViewController {
|
|||||||
if (isNaN(h)) {
|
if (isNaN(h)) {
|
||||||
Log.wtf(TAG, "ExpandedHeight set to NaN");
|
Log.wtf(TAG, "ExpandedHeight set to NaN");
|
||||||
}
|
}
|
||||||
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
|
mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
|
||||||
if (mExpandLatencyTracking && h != 0f) {
|
if (mExpandLatencyTracking && h != 0f) {
|
||||||
DejankUtils.postAfterTraversal(
|
DejankUtils.postAfterTraversal(
|
||||||
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
|
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
|
||||||
@@ -5103,7 +5141,7 @@ public final class NotificationPanelViewController {
|
|||||||
/**
|
/**
|
||||||
* Create an animator that can also overshoot
|
* Create an animator that can also overshoot
|
||||||
*
|
*
|
||||||
* @param targetHeight the target height
|
* @param targetHeight the target height
|
||||||
* @param overshootAmount the amount of overshoot desired
|
* @param overshootAmount the amount of overshoot desired
|
||||||
*/
|
*/
|
||||||
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
|
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
|
||||||
@@ -5900,7 +5938,7 @@ public final class NotificationPanelViewController {
|
|||||||
public final class TouchHandler implements View.OnTouchListener {
|
public final class TouchHandler implements View.OnTouchListener {
|
||||||
private long mLastTouchDownTime = -1L;
|
private long mLastTouchDownTime = -1L;
|
||||||
|
|
||||||
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
|
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
|
||||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||||
if (SPEW_LOGCAT) {
|
if (SPEW_LOGCAT) {
|
||||||
Log.v(TAG,
|
Log.v(TAG,
|
||||||
@@ -6099,7 +6137,7 @@ public final class NotificationPanelViewController {
|
|||||||
mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
|
mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
|
if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
|
||||||
mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
|
mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -6256,4 +6294,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.animation.TextAnimator
|
import com.android.systemui.animation.TextAnimator
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -55,7 +56,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
|
|||||||
clockView.animateAppearOnLockscreen()
|
clockView.animateAppearOnLockscreen()
|
||||||
clockView.measure(50, 50)
|
clockView.measure(50, 50)
|
||||||
|
|
||||||
verify(mockTextAnimator).glyphFilter = null
|
verify(mockTextAnimator).glyphFilter = any()
|
||||||
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
|
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
|
||||||
verifyNoMoreInteractions(mockTextAnimator)
|
verifyNoMoreInteractions(mockTextAnimator)
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
|
|||||||
clockView.measure(50, 50)
|
clockView.measure(50, 50)
|
||||||
clockView.animateAppearOnLockscreen()
|
clockView.animateAppearOnLockscreen()
|
||||||
|
|
||||||
verify(mockTextAnimator, times(2)).glyphFilter = null
|
verify(mockTextAnimator, times(2)).glyphFilter = any()
|
||||||
verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
|
verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
|
||||||
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
|
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
|
||||||
verifyNoMoreInteractions(mockTextAnimator)
|
verifyNoMoreInteractions(mockTextAnimator)
|
||||||
|
|||||||
Reference in New Issue
Block a user