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

Commit 686af64b authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

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

parents a439e9a6 48fb7c5d
Loading
Loading
Loading
Loading
+47 −119
Original line number Original line Diff line number Diff line
@@ -22,12 +22,11 @@ import android.annotation.IntRange
import android.annotation.SuppressLint
import android.annotation.SuppressLint
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.util.MathUtils.constrainedMap
import android.widget.TextView
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.GlyphCallback
@@ -40,8 +39,6 @@ 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)
@@ -478,122 +475,56 @@ class AnimatableClockView @JvmOverloads constructor(
        pw.println("    time=$time")
        pw.println("    time=$time")
    }
    }


    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
    private val moveToCenterDelays
        // Do we need to cancel an in-flight animation?
        get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
        // 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.
    private val moveToSideDelays
            val moveAmount = toRect.left - fromRect.left
        get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS

            // 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)


    /**
     * 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) {
        for (i in 0 until NUM_DIGITS) {
                val stopAmount = if (toRect.left > fromRect.left) {
            // The delay for the digit, in terms of fraction (i.e. the digit should not move
                    // 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).
            // during 0.0 - 0.1).
                val initialDelay = if (toRect.left > fromRect.left) {
            val digitInitialDelay =
                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
                    if (isMovingToCenter) {
                        moveToCenterDelays[i] * MOVE_DIGIT_STEP
                    } else {
                    } else {
                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
                        moveToSideDelays[i] * MOVE_DIGIT_STEP
                }
                    }

            val digitFraction =
                val f = MathUtils.constrainedMap(
                    MOVE_INTERPOLATOR.getInterpolation(
                        0.0f, 1.0f,
                            constrainedMap(
                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
                                    0.0f,
                        actualFraction
                                    1.0f,
                                    digitInitialDelay,
                                    digitInitialDelay + AVAILABLE_ANIMATION_TIME,
                                    moveFraction
                            )
                            )
                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
                    )
            }
            val moveAmountForDigit = currentMoveAmount * digitFraction

            val moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
            // Was there an animation halt?
            glyphOffsets[i] = digitOffsetDirection * moveAmountDeltaForDigit
            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()
        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.
@@ -647,9 +578,6 @@ class AnimatableClockView @JvmOverloads constructor(
        private const val NUM_DIGITS = 4
        private const val NUM_DIGITS = 4
        private const val DIGITS_PER_LINE = 2
        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
        // 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.
        // "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
        // 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
        // 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.
        // 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 Original line Diff line number Diff line
@@ -189,8 +189,9 @@ class DefaultClockController(
            view.setLayoutParams(lp)
            view.setLayoutParams(lp)
        }
        }


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


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


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


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

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


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


    @VisibleForTesting
    @VisibleForTesting
    static class SplitShadeTransitionAdapter extends Transition {
    static class SplitShadeTransitionAdapter extends Transition {
        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
        private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
        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;
        private final KeyguardClockSwitchController mController;


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


        private void captureValues(TransitionValues transitionValues) {
        private void captureValues(TransitionValues transitionValues) {
            Rect boundsRect = new Rect();
            transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft());
            boundsRect.left = transitionValues.view.getLeft();
            int[] locationInWindowTmp = new int[2];
            boundsRect.top = transitionValues.view.getTop();
            transitionValues.view.getLocationInWindow(locationInWindowTmp);
            boundsRect.right = transitionValues.view.getRight();
            transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]);
            boundsRect.bottom = transitionValues.view.getBottom();
            transitionValues.values.put(PROP_BOUNDS, boundsRect);
        }
        }


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


            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
            int fromLeft = (int) startValues.values.get(PROP_BOUNDS_LEFT);
            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
            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 -> {
            anim.addUpdateListener(animation -> {
                ClockController clock = mController.getClock();
                ClockController clock = mController.getClock();
@@ -437,7 +438,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
                }
                }


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


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


    @Test
    @Test
    fun createAnimator_nullStartValues_returnsNull() {
    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()
        assertThat(animator).isNull()
    }
    }


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


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


        assertThat(animator).isNotNull()
        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(
private fun SplitShadeTransitionAdapter.createAnimator(