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

Commit 7395cf10 authored by George Mount's avatar George Mount Committed by Android (Google) Code Review
Browse files

Merge changes from topic "revert-20986099-revert-20590670-animator_seeking-RVESPLGWYK-UCBFNOEOVV"

* changes:
  Revert^2 "Animator start and stop notifications during seek"
  Revert^2 "Improve AnimatorSet seekability."
parents 983f301b d458cc3e
Loading
Loading
Loading
Loading
+30 −3
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
import android.os.Build;
import android.util.LongArray;

import java.util.ArrayList;

@@ -546,7 +547,6 @@ public abstract class Animator implements Cloneable {
     */
    void skipToEndValue(boolean inReverse) {}


    /**
     * Internal use only.
     *
@@ -559,9 +559,36 @@ public abstract class Animator implements Cloneable {
    }

    /**
     * Internal use only.
     * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
     * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
     * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
     * done between the two times.
     */
    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}

    /**
     * Internal use only. This animates any animation that has ended since lastPlayTime.
     * If an animation hasn't been finished, no change will be made.
     */
    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}

    /**
     * Internal use only. Adds all start times (after delay) to and end times to times.
     * The value must include offset.
     */
    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
    void getStartAndEndTimes(LongArray times, long offset) {
        long startTime = offset + getStartDelay();
        if (times.indexOf(startTime) < 0) {
            times.add(startTime);
        }
        long duration = getTotalDuration();
        if (duration != DURATION_INFINITE) {
            long endTime = duration + offset;
            if (times.indexOf(endTime) < 0) {
                times.add(endTime);
            }
        }
    }

    /**
     * <p>An animation listener receives notifications from an animation.
+266 −109
Original line number Diff line number Diff line
@@ -23,9 +23,11 @@ import android.os.Looper;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongArray;
import android.view.animation.Animation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -181,6 +183,16 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
     */
    private long mPauseTime = -1;

    /**
     * The start and stop times of all descendant animators.
     */
    private long[] mChildStartAndStopTimes;

    /**
     * Tracks whether we've notified listeners of the onAnimationStart() event.
     */
    private boolean mStartListenersCalled;

    // This is to work around a bug in b/34736819. This needs to be removed once app team
    // fixes their side.
    private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -729,19 +741,38 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
            startAnimation();
        }

        if (mListeners != null) {
        notifyStartListeners(inReverse);
        if (isEmptySet) {
            // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
            // onAnimationEnd() right away.
            end();
        }
    }

    private void notifyStartListeners(boolean inReverse) {
        if (mListeners != null && !mStartListenersCalled) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this, inReverse);
                AnimatorListener listener = tmpListeners.get(i);
                listener.onAnimationStart(this, inReverse);
            }
        }
        if (isEmptySet) {
            // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
            // onAnimationEnd() right away.
            end();
        mStartListenersCalled = true;
    }

    private void notifyEndListeners(boolean inReverse) {
        if (mListeners != null && mStartListenersCalled) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                AnimatorListener listener = tmpListeners.get(i);
                listener.onAnimationEnd(this, inReverse);
            }
        }
        mStartListenersCalled = false;
    }

    // Returns true if set is empty or contains nothing but animator sets with no start delay.
@@ -779,26 +810,25 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim

    @Override
    void skipToEndValue(boolean inReverse) {
        if (!isInitialized()) {
            throw new UnsupportedOperationException("Children must be initialized.");
        }

        // This makes sure the animation events are sorted an up to date.
        initAnimation();
        initChildren();

        // Calling skip to the end in the sequence that they would be called in a forward/reverse
        // run, such that the sequential animations modifying the same property would have
        // the right value in the end.
        if (inReverse) {
            for (int i = mEvents.size() - 1; i >= 0; i--) {
                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
                AnimationEvent event = mEvents.get(i);
                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
                    event.mNode.mAnimation.skipToEndValue(true);
                }
            }
        } else {
            for (int i = 0; i < mEvents.size(); i++) {
                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
                    mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
                AnimationEvent event = mEvents.get(i);
                if (event.mEvent == AnimationEvent.ANIMATION_END) {
                    event.mNode.mAnimation.skipToEndValue(false);
                }
            }
        }
@@ -814,72 +844,216 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
     * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
     * as needed, based on the last play time and current play time.
     */
    @Override
    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
        if (currentPlayTime < 0 || lastPlayTime < 0) {
    private void animateBasedOnPlayTime(
            long currentPlayTime,
            long lastPlayTime,
            boolean inReverse,
            boolean notify
    ) {
        if (currentPlayTime < 0 || lastPlayTime < -1) {
            throw new UnsupportedOperationException("Error: Play time should never be negative.");
        }
        // TODO: take into account repeat counts and repeat callback when repeat is implemented.
        // Clamp currentPlayTime and lastPlayTime

        // TODO: Make this more efficient

        // Convert the play times to the forward direction.
        if (inReverse) {
            if (getTotalDuration() == DURATION_INFINITE) {
                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
                        + " duration");
            long duration = getTotalDuration();
            if (duration == DURATION_INFINITE) {
                throw new UnsupportedOperationException(
                        "Cannot reverse AnimatorSet with infinite duration"
                );
            }
            long duration = getTotalDuration() - mStartDelay;
            // Convert the play times to the forward direction.
            currentPlayTime = Math.min(currentPlayTime, duration);
            currentPlayTime = duration - currentPlayTime;
            lastPlayTime = duration - lastPlayTime;
            inReverse = false;
        }

        ArrayList<Node> unfinishedNodes = new ArrayList<>();
        // Assumes forward playing from here on.
        for (int i = 0; i < mEvents.size(); i++) {
            AnimationEvent event = mEvents.get(i);
            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
                break;
        long[] startEndTimes = ensureChildStartAndEndTimes();
        int index = findNextIndex(lastPlayTime, startEndTimes);
        int endIndex = findNextIndex(currentPlayTime, startEndTimes);

        // Change values at the start/end times so that values are set in the right order.
        // We don't want an animator that would finish before another to override the value
        // set by another animator that finishes earlier.
        if (currentPlayTime >= lastPlayTime) {
            while (index < endIndex) {
                long playTime = startEndTimes[index];
                if (lastPlayTime != playTime) {
                    animateSkipToEnds(playTime, lastPlayTime, notify);
                    animateValuesInRange(playTime, lastPlayTime, notify);
                    lastPlayTime = playTime;
                }
                index++;
            }
        } else {
            while (index > endIndex) {
                index--;
                long playTime = startEndTimes[index];
                if (lastPlayTime != playTime) {
                    animateSkipToEnds(playTime, lastPlayTime, notify);
                    animateValuesInRange(playTime, lastPlayTime, notify);
                    lastPlayTime = playTime;
                }
            }
        }
        if (currentPlayTime != lastPlayTime) {
            animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
            animateValuesInRange(currentPlayTime, lastPlayTime, notify);
        }
    }

            // This animation started prior to the current play time, and won't finish before the
            // play time, add to the unfinished list.
            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
                if (event.mNode.mEndTime == DURATION_INFINITE
                        || event.mNode.mEndTime > currentPlayTime) {
                    unfinishedNodes.add(event.mNode);
    /**
     * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
     * is returned. Otherwise, it returns the index at which it would be placed if it were
     * to be inserted.
     */
    private int findNextIndex(long playTime, long[] startEndTimes) {
        int index = Arrays.binarySearch(startEndTimes, playTime);
        if (index < 0) {
            index = -index - 1;
        } else {
            index++;
        }
        return index;
    }
            // For animations that do finish before the play time, end them in the sequence that
            // they would in a normal run.
            if (event.mEvent == AnimationEvent.ANIMATION_END) {
                // Skip to the end of the animation.
                event.mNode.mAnimation.skipToEndValue(false);

    @Override
    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
        initAnimation();

        if (lastPlayTime > currentPlayTime) {
            if (notify) {
                notifyStartListeners(true);
            }
            for (int i = mEvents.size() - 1; i >= 0; i--) {
                AnimationEvent event = mEvents.get(i);
                Node node = event.mNode;
                if (event.mEvent == AnimationEvent.ANIMATION_END
                        && node.mStartTime != DURATION_INFINITE
                ) {
                    Animator animator = node.mAnimation;
                    long start = node.mStartTime;
                    long end = node.mTotalDuration == DURATION_INFINITE
                            ? Long.MAX_VALUE : node.mEndTime;
                    if (currentPlayTime <= start && start < lastPlayTime) {
                        animator.animateSkipToEnds(
                                0,
                                lastPlayTime - node.mStartTime,
                                notify
                        );
                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
                        animator.animateSkipToEnds(
                                currentPlayTime - node.mStartTime,
                                lastPlayTime - node.mStartTime,
                                notify
                        );
                    }
                }
            }
            if (currentPlayTime <= 0 && notify) {
                notifyEndListeners(true);
            }
        } else {
            if (notify) {
                notifyStartListeners(false);
            }
            int eventsSize = mEvents.size();
            for (int i = 0; i < eventsSize; i++) {
                AnimationEvent event = mEvents.get(i);
                Node node = event.mNode;
                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
                        && node.mStartTime != DURATION_INFINITE
                ) {
                    Animator animator = node.mAnimation;
                    long start = node.mStartTime;
                    long end = node.mTotalDuration == DURATION_INFINITE
                            ? Long.MAX_VALUE : node.mEndTime;
                    if (lastPlayTime < end && end <= currentPlayTime) {
                        animator.animateSkipToEnds(
                                end - node.mStartTime,
                                lastPlayTime - node.mStartTime,
                                notify
                        );
                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
                        animator.animateSkipToEnds(
                                currentPlayTime - node.mStartTime,
                                lastPlayTime - node.mStartTime,
                                notify
                        );
                    }
                }
            }
            if (currentPlayTime >= getTotalDuration() && notify) {
                notifyEndListeners(false);
            }
        }
    }

    @Override
    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
        initAnimation();

        // Seek unfinished animation to the right time.
        for (int i = 0; i < unfinishedNodes.size(); i++) {
            Node node = unfinishedNodes.get(i);
            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
            if (!inReverse) {
                playTime -= node.mAnimation.getStartDelay();
        if (notify) {
            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
                notifyStartListeners(false);
            } else {
                long duration = getTotalDuration();
                if (duration >= 0
                        && (lastPlayTime > duration || (lastPlayTime == duration
                        && currentPlayTime < duration))
                ) {
                    notifyStartListeners(true);
                }
            }
            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
        }

        // Seek not yet started animations.
        for (int i = 0; i < mEvents.size(); i++) {
        int eventsSize = mEvents.size();
        for (int i = 0; i < eventsSize; i++) {
            AnimationEvent event = mEvents.get(i);
            if (event.getTime() > currentPlayTime
                    && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
                event.mNode.mAnimation.skipToEndValue(true);
            Node node = event.mNode;
            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
                    && node.mStartTime != DURATION_INFINITE
            ) {
                Animator animator = node.mAnimation;
                long start = node.mStartTime;
                long end = node.mTotalDuration == DURATION_INFINITE
                        ? Long.MAX_VALUE : node.mEndTime;
                if ((start < currentPlayTime && currentPlayTime < end)
                        || (start == currentPlayTime && lastPlayTime < start)
                        || (end == currentPlayTime && lastPlayTime > end)
                ) {
                    animator.animateValuesInRange(
                            currentPlayTime - node.mStartTime,
                            Math.max(-1, lastPlayTime - node.mStartTime),
                            notify
                    );
                }
            }
        }
    }

    private long[] ensureChildStartAndEndTimes() {
        if (mChildStartAndStopTimes == null) {
            LongArray startAndEndTimes = new LongArray();
            getStartAndEndTimes(startAndEndTimes, 0);
            long[] times = startAndEndTimes.toArray();
            Arrays.sort(times);
            mChildStartAndStopTimes = times;
        }
        return mChildStartAndStopTimes;
    }

    @Override
    void getStartAndEndTimes(LongArray times, long offset) {
        int eventsSize = mEvents.size();
        for (int i = 0; i < eventsSize; i++) {
            AnimationEvent event = mEvents.get(i);
            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
                    && event.mNode.mStartTime != DURATION_INFINITE
            ) {
                event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
            }
        }
    }

    @Override
@@ -899,10 +1073,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        return mChildrenInitialized;
    }

    private void skipToStartValue(boolean inReverse) {
        skipToEndValue(!inReverse);
    }

    /**
     * Sets the position of the animation to the specified point in time. This time should
     * be between 0 and the total duration of the animation, including any repetition. If
@@ -910,6 +1080,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
     * set to this time; it will simply set the time to this value and perform any appropriate
     * actions based on that time. If the animation is already running, then setCurrentPlayTime()
     * will set the current playing time to this value and continue playing from that point.
     * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
     * that hasn't been {@link #start()}ed, will issue
     * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
     * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
     * events.
     *
     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
     *                 Unless the animation is reversing, the playtime is considered the time since
@@ -931,24 +1106,22 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim

        initAnimation();

        long lastPlayTime = mSeekState.getPlayTime();
        if (!isStarted() || isPaused()) {
            if (mReversing) {
            if (mReversing && !isStarted()) {
                throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
                        + " should not be set when AnimatorSet is not started.");
            }
            if (!mSeekState.isActive()) {
                findLatestEventIdForTime(0);
                // Set all the values to start values.
                initChildren();
                // Set all the values to start values.
                skipToEndValue(!mReversing);
                mSeekState.setPlayTime(0, mReversing);
            }
            animateBasedOnPlayTime(playTime, 0, mReversing);
            mSeekState.setPlayTime(playTime, mReversing);
        } else {
            // If the animation is running, just set the seek time and wait until the next frame
            // (i.e. doAnimationFrame(...)) to advance the animation.
            mSeekState.setPlayTime(playTime, mReversing);
        }
        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
        mSeekState.setPlayTime(playTime, mReversing);
    }

    /**
@@ -981,10 +1154,16 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
    private void initChildren() {
        if (!isInitialized()) {
            mChildrenInitialized = true;
            // Forcefully initialize all children based on their end time, so that if the start
            // value of a child is dependent on a previous animation, the animation will be
            // initialized after the the previous animations have been advanced to the end.
            skipToEndValue(false);

            // We have to initialize all the start values so that they are based on the previous
            // values.
            long[] times = ensureChildStartAndEndTimes();

            long previousTime = -1;
            for (long time : times) {
                animateBasedOnPlayTime(time, previousTime, false, false);
                previousTime = time;
            }
        }
    }

@@ -1058,7 +1237,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        for (int i = 0; i < mPlayingSet.size(); i++) {
            Node node = mPlayingSet.get(i);
            if (!node.mEnded) {
                pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
                pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
            }
        }

@@ -1129,7 +1308,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
                    pulseFrame(node, 0);
                } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
                    // end event:
                    pulseFrame(node, getPlayTimeForNode(playTime, node));
                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                }
            }
        } else {
@@ -1150,7 +1329,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
                    pulseFrame(node, 0);
                } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
                    // start event:
                    pulseFrame(node, getPlayTimeForNode(playTime, node));
                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                }
            }
        }
@@ -1172,11 +1351,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        }
    }

    private long getPlayTimeForNode(long overallPlayTime, Node node) {
        return getPlayTimeForNode(overallPlayTime, node, mReversing);
    private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
        return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
    }

    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
    private long getPlayTimeForNodeIncludingDelay(
            long overallPlayTime,
            Node node,
            boolean inReverse
    ) {
        if (inReverse) {
            overallPlayTime = getTotalDuration() - overallPlayTime;
            return node.mEndTime - overallPlayTime;
@@ -1198,26 +1381,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        }
        // Set the child animators to the right end:
        if (mShouldResetValuesAtStart) {
            if (isInitialized()) {
                skipToEndValue(!mReversing);
            } else if (mReversing) {
                // Reversing but haven't initialized all the children yet.
            initChildren();
            skipToEndValue(!mReversing);
            } else {
                // If not all children are initialized and play direction is forward
                for (int i = mEvents.size() - 1; i >= 0; i--) {
                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
                        Animator anim = mEvents.get(i).mNode.mAnimation;
                        // Only reset the animations that have been initialized to start value,
                        // so that if they are defined without a start value, they will get the
                        // values set at the right time (i.e. the next animation run)
                        if (anim.isInitialized()) {
                            anim.skipToEndValue(true);
                        }
                    }
                }
            }
        }

        if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1292,15 +1457,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim

        // No longer receive callbacks
        removeAnimationCallback();
        // Call end listener
        if (mListeners != null) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationEnd(this, mReversing);
            }
        }
        notifyEndListeners(mReversing);
        removeAnimationEndListener();
        mSelfPulse = true;
        mReversing = false;
@@ -1922,11 +2079,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        }

        void setPlayTime(long playTime, boolean inReverse) {
            // TODO: This can be simplified.

            // Clamp the play time
            if (getTotalDuration() != DURATION_INFINITE) {
                mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
            } else {
                mPlayTime = playTime;
            }
            mPlayTime = Math.max(0, mPlayTime);
            mSeekingInReverse = inReverse;