Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 48fb7c5d authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Step clock animation: fix RTL issues and clean-up a little" into udc-dev

parents 322a9ee2 f5bc9b73
Loading
Loading
Loading
Loading
+47 −119
Original line number Diff line number Diff line
@@ -22,12 +22,11 @@ import android.annotation.IntRange
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.text.Layout
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.util.MathUtils
import android.util.MathUtils.constrainedMap
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
@@ -40,8 +39,6 @@ import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
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)
@@ -478,122 +475,56 @@ class AnimatableClockView @JvmOverloads constructor(
        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
    private val moveToCenterDelays
        get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS

            // 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)
    private val moveToSideDelays
        get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS

    /**
     * Offsets the glyphs of the clock for the step clock animation.
     *
     * The animation makes the glyphs of the clock move at different speeds, when the clock is
     * moving horizontally.
     *
     * @param clockStartLeft the [getLeft] position of the clock, before it started moving.
     * @param clockMoveDirection the direction in which it is moving. A positive number means right,
     *   and negative means left.
     * @param moveFraction fraction of the clock movement. 0 means it is at the beginning, and 1
     *   means it finished moving.
     */
    fun offsetGlyphsForStepClockAnimation(
            clockStartLeft: Int,
            clockMoveDirection: Int,
            moveFraction: Float
    ) {
        val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
        val currentMoveAmount = left - clockStartLeft
        val digitOffsetDirection = if (isLayoutRtl) -1 else 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
            // The delay for the 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
            val digitInitialDelay =
                    if (isMovingToCenter) {
                        moveToCenterDelays[i] * 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
                        moveToSideDelays[i] * MOVE_DIGIT_STEP
                    }
            val digitFraction =
                    MOVE_INTERPOLATOR.getInterpolation(
                            constrainedMap(
                                    0.0f,
                                    1.0f,
                                    digitInitialDelay,
                                    digitInitialDelay + AVAILABLE_ANIMATION_TIME,
                                    moveFraction
                            )
                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])
            }
                    )
            val moveAmountForDigit = currentMoveAmount * digitFraction
            val moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
            glyphOffsets[i] = digitOffsetDirection * moveAmountDeltaForDigit
        }

        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.
@@ -647,9 +578,6 @@ class AnimatableClockView @JvmOverloads constructor(
        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 = 0f

        // 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
@@ -668,6 +596,6 @@ class AnimatableClockView @JvmOverloads constructor(

        // 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)
        private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -189,8 +189,9 @@ class DefaultClockController(
            view.setLayoutParams(lp)
        }

        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
            view.moveForSplitShade(fromRect, toRect, fraction)
        /** See documentation at [AnimatableClockView.offsetGlyphsForStepClockAnimation]. */
        fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) {
            view.offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
        }
    }

@@ -277,8 +278,8 @@ class DefaultClockController(
        dozeFraction: Float,
        foldFraction: Float,
    ) : DefaultClockAnimations(view, dozeFraction, foldFraction) {
        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
            largeClock.moveForSplitShade(fromRect, toRect, fraction)
        override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
            largeClock.offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
        }
    }

+10 −2
Original line number Diff line number Diff line
@@ -141,8 +141,16 @@ interface ClockAnimations {
    /** Runs the battery animation (if any). */
    fun charge() {}

    /** Move the clock, for example, if the notification tray appears in split-shade mode. */
    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
    /**
     * Runs when the clock's position changed during the move animation.
     *
     * @param fromLeft the [View.getLeft] position of the clock, before it started moving.
     * @param direction the direction in which it is moving. A positive number means right, and
     *   negative means left.
     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
     *   it finished moving.
     */
    fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}

    /**
     * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
+14 −13
Original line number Diff line number Diff line
@@ -363,8 +363,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
            } else {
                View clockView = clockContainerView.getChildAt(0);

                transition.excludeTarget(clockView, /* exclude= */ true);

                TransitionSet set = new TransitionSet();
                set.addTransition(transition);

@@ -389,8 +387,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV

    @VisibleForTesting
    static class SplitShadeTransitionAdapter extends Transition {
        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
        private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
        private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow";
        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW};

        private final KeyguardClockSwitchController mController;

@@ -400,12 +399,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
        }

        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);
            transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft());
            int[] locationInWindowTmp = new int[2];
            transitionValues.view.getLocationInWindow(locationInWindowTmp);
            transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]);
        }

        @Override
@@ -427,8 +424,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
            }
            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);

            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
            int fromLeft = (int) startValues.values.get(PROP_BOUNDS_LEFT);
            int fromWindowX = (int) startValues.values.get(PROP_X_IN_WINDOW);
            int toWindowX = (int) endValues.values.get(PROP_X_IN_WINDOW);
            // Using windowX, to determine direction, instead of left, as in RTL the difference of
            // toLeft - fromLeft is always positive, even when moving left.
            int direction = toWindowX - fromWindowX > 0 ? 1 : -1;

            anim.addUpdateListener(animation -> {
                ClockController clock = mController.getClock();
@@ -437,7 +438,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
                }

                clock.getLargeClock().getAnimations()
                        .onPositionUpdated(from, to, animation.getAnimatedFraction());
                        .onPositionUpdated(fromLeft, direction, animation.getAnimatedFraction());
            });

            return anim;
+18 −3
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.keyguard
import android.animation.Animator
import android.testing.AndroidTestingRunner
import android.transition.TransitionValues
import android.view.View
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
import com.android.systemui.SysuiTestCase
@@ -44,14 +45,16 @@ class SplitShadeTransitionAdapterTest : SysuiTestCase() {

    @Test
    fun createAnimator_nullStartValues_returnsNull() {
        val animator = adapter.createAnimator(startValues = null, endValues = TransitionValues())
        val endValues = createEndValues()

        val animator = adapter.createAnimator(startValues = null, endValues = endValues)

        assertThat(animator).isNull()
    }

    @Test
    fun createAnimator_nullEndValues_returnsNull() {
        val animator = adapter.createAnimator(startValues = TransitionValues(), endValues = null)
        val animator = adapter.createAnimator(startValues = createStartValues(), endValues = null)

        assertThat(animator).isNull()
    }
@@ -59,10 +62,22 @@ class SplitShadeTransitionAdapterTest : SysuiTestCase() {
    @Test
    fun createAnimator_nonNullStartAndEndValues_returnsAnimator() {
        val animator =
            adapter.createAnimator(startValues = TransitionValues(), endValues = TransitionValues())
            adapter.createAnimator(startValues = createStartValues(), endValues = createEndValues())

        assertThat(animator).isNotNull()
    }

    private fun createStartValues() =
        TransitionValues().also { values ->
            values.view = View(context)
            adapter.captureStartValues(values)
        }

    private fun createEndValues() =
        TransitionValues().also { values ->
            values.view = View(context)
            adapter.captureEndValues(values)
        }
}

private fun SplitShadeTransitionAdapter.createAnimator(