android.animation.AnimationHandler

Here are the examples of the java api android.animation.AnimationHandler taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

2 Examples 7

18 Source : BoundsAnimationController.java
with Apache License 2.0
from lulululbj

/**
 * Enables animating bounds of objects.
 *
 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
 * relaunching it would cause poorer experience), these clreplaced provides a way to directly animate
 * the bounds of the resized object.
 *
 * The object that is resized needs to implement {@link BoundsAnimationTarget} interface.
 *
 * NOTE: All calls to methods in this clreplaced should be done on the Animation thread
 */
public clreplaced BoundsAnimationController {

    private static final boolean DEBUG_LOCAL = false;

    private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;

    private static final String TAG = TAG_WITH_CLreplaced_NAME || DEBUG_LOCAL ? "BoundsAnimationController" : TAG_WM;

    private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;

    private static final int DEFAULT_TRANSITION_DURATION = 425;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START, SCHEDULE_PIP_MODE_CHANGED_ON_END })
    public @interface SchedulePipModeChangedState {
    }

    /**
     * Do not schedule any PiP mode changed callbacks as a part of this animation.
     */
    public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;

    /**
     * Schedule a PiP mode changed callback when this animation starts.
     */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;

    /**
     * Schedule a PiP mode changed callback when this animation ends.
     */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;

    // Only accessed on UI thread.
    private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();

    private final clreplaced AppTransitionNotifier extends WindowManagerInternal.AppTransitionListener implements Runnable {

        public void onAppTransitionCancelledLocked() {
            if (DEBUG)
                Slog.d(TAG, "onAppTransitionCancelledLocked:" + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
            animationFinished();
        }

        public void onAppTransitionFinishedLocked(IBinder token) {
            if (DEBUG)
                Slog.d(TAG, "onAppTransitionFinishedLocked:" + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
            animationFinished();
        }

        private void animationFinished() {
            if (mFinishAnimationAfterTransition) {
                mHandler.removeCallbacks(this);
                // This might end up calling into activity manager which will be bad since we have
                // the window manager lock held at this point. Post a message to take care of the
                // processing so we don't deadlock.
                mHandler.post(this);
            }
        }

        @Override
        public void run() {
            for (int i = 0; i < mRunningAnimations.size(); i++) {
                final BoundsAnimator b = mRunningAnimations.valueAt(i);
                b.onAnimationEnd(null);
            }
        }
    }

    private final Handler mHandler;

    private final AppTransition mAppTransition;

    private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();

    private final Interpolator mFastOutSlowInInterpolator;

    private boolean mFinishAnimationAfterTransition = false;

    private final AnimationHandler mAnimationHandler;

    private Creplacedographer mCreplacedographer;

    private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000;

    BoundsAnimationController(Context context, AppTransition transition, Handler handler, AnimationHandler animationHandler) {
        mHandler = handler;
        mAppTransition = transition;
        mAppTransition.registerListenerLocked(mAppTransitionNotifier);
        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in);
        mAnimationHandler = animationHandler;
        if (animationHandler != null) {
            // If an animation handler is provided, then ensure that it runs on the sf vsync tick
            handler.runWithScissors(() -> mCreplacedographer = Creplacedographer.getSfInstance(), 0);
            animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mCreplacedographer));
        }
    }

    @VisibleForTesting
    final clreplaced BoundsAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {

        private final BoundsAnimationTarget mTarget;

        private final Rect mFrom = new Rect();

        private final Rect mTo = new Rect();

        private final Rect mTmpRect = new Rect();

        private final Rect mTmpTaskBounds = new Rect();

        // True if this this animation was canceled and will be replaced the another animation from
        // the same {@link #BoundsAnimationTarget} target.
        private boolean mSkipFinalResize;

        // True if this animation was canceled by the user, not as a part of a replacing animation
        private boolean mSkipAnimationEnd;

        // True if the animation target is animating from the fullscreen. Only one of
        // {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be true at any time in the
        // animation.
        private boolean mMoveFromFullscreen;

        // True if the animation target should be moved to the fullscreen stack at the end of this
        // animation. Only one of {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be
        // true at any time in the animation.
        private boolean mMoveToFullscreen;

        // Whether to schedule PiP mode changes on animation start/end
        @SchedulePipModeChangedState
        private int mSchedulePipModeChangedState;

        @SchedulePipModeChangedState
        private int mPrevSchedulePipModeChangedState;

        // Depending on whether we are animating from
        // a smaller to a larger size
        private final int mFrozenTaskWidth;

        private final int mFrozenTaskHeight;

        // Timeout callback to ensure we continue the animation if waiting for resuming or app
        // windows drawn fails
        private final Runnable mResumeRunnable = () -> {
            if (DEBUG)
                Slog.d(TAG, "pause: timed out waiting for windows drawn");
            resume();
        };

        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, @SchedulePipModeChangedState int prevShedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen) {
            super();
            mTarget = target;
            mFrom.set(from);
            mTo.set(to);
            mSchedulePipModeChangedState = schedulePipModeChangedState;
            mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
            mMoveFromFullscreen = moveFromFullscreen;
            mMoveToFullscreen = moveToFullscreen;
            addUpdateListener(this);
            addListener(this);
            // If we are animating from smaller to larger, we want to change the task bounds
            // to their final size immediately so we can use scaling to make the window
            // larger. Likewise if we are going from bigger to smaller, we want to wait until
            // the end so we don't have to upscale from the smaller finished size.
            if (animatingToLargerSize()) {
                mFrozenTaskWidth = mTo.width();
                mFrozenTaskHeight = mTo.height();
            } else {
                mFrozenTaskWidth = mFrom.width();
                mFrozenTaskHeight = mFrom.height();
            }
        }

        @Override
        public void onAnimationStart(Animator animation) {
            if (DEBUG)
                Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
            mFinishAnimationAfterTransition = false;
            mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
            // Boost the thread priority of the animation thread while the bounds animation is
            // running
            updateBooster();
            // Ensure that we have prepared the target for animation before we trigger any size
            // changes, so it can swap surfaces in to appropriate modes, or do as it wishes
            // otherwise.
            if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
                mTarget.onAnimationStart(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START, false);
                // When starting an animation from fullscreen, pause here and wait for the
                // windows-drawn signal before we start the rest of the transition down into PiP.
                if (mMoveFromFullscreen && mTarget.shouldDeferStartOnMoveToFullscreen()) {
                    pause();
                }
            } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END && mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                // We are replacing a running animation into PiP, but since it hasn't completed, the
                // client will not currently receive any picture-in-picture mode change callbacks.
                // However, we still need to report to them that they are leaving PiP, so this will
                // force an update via a mode changed callback.
                mTarget.onAnimationStart(true, /* schedulePipModeChangedCallback */
                true);
            }
            // Immediately update the task bounds if they have to become larger, but preserve
            // the starting position so we don't jump at the beginning of the animation.
            if (animatingToLargerSize()) {
                mTarget.setPinnedStackSize(mFrom, mTmpRect);
                // We pause the animation until the app has drawn at the new size.
                // The target will notify us via BoundsAnimationController#resume.
                // We do this here and pause the animation, rather than just defer starting it
                // so we can enter the animating state and have WindowStateAnimator apply the
                // correct logic to make this resize seamless.
                if (mMoveToFullscreen) {
                    pause();
                }
            }
        }

        @Override
        public void pause() {
            if (DEBUG)
                Slog.d(TAG, "pause: waiting for windows drawn");
            super.pause();
            mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
        }

        @Override
        public void resume() {
            if (DEBUG)
                Slog.d(TAG, "resume:");
            mHandler.removeCallbacks(mResumeRunnable);
            super.resume();
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            final float value = (Float) animation.getAnimatedValue();
            final float remains = 1 - value;
            mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
            mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
            mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
            mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
            if (DEBUG)
                Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds=" + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value + " remains=" + remains);
            mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top, mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
            if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
                // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
                // any further animation.
                if (DEBUG)
                    Slog.d(TAG, "animateUpdate: cancelled");
                // If we have already scheduled a PiP mode changed at the start of the animation,
                // then we need to clean up and schedule one at the end, since we have canceled the
                // animation to the final state.
                if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }
                // Since we are cancelling immediately without a replacement animation, send the
                // animation end to maintain callback parity, but also skip any further resizes
                cancelAndCallAnimationEnd();
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if (DEBUG)
                Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " mSkipFinalResize=" + mSkipFinalResize + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition + " mAppTransitionIsRunning=" + mAppTransition.isRunning() + " callers=" + Debug.getCallers(2));
            // There could be another animation running. For example in the
            // move to fullscreen case, recents will also be closing while the
            // previous task will be taking its place in the fullscreen stack.
            // we have to ensure this is completed before we finish the animation
            // and take our place in the fullscreen stack.
            if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
                mFinishAnimationAfterTransition = true;
                return;
            }
            if (!mSkipAnimationEnd) {
                // If this animation has already scheduled the picture-in-picture mode on start, and
                // we are not skipping the final resize due to being canceled, then move the PiP to
                // fullscreen once the animation ends
                if (DEBUG)
                    Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " moveToFullscreen=" + mMoveToFullscreen);
                mTarget.onAnimationEnd(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null, mMoveToFullscreen);
            }
            // Clean up this animation
            removeListener(this);
            removeUpdateListener(this);
            mRunningAnimations.remove(mTarget);
            // Reset the thread priority of the animation thread after the bounds animation is done
            updateBooster();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            // Always skip the final resize when the animation is canceled
            mSkipFinalResize = true;
            mMoveToFullscreen = false;
        }

        private void cancelAndCallAnimationEnd() {
            if (DEBUG)
                Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget);
            mSkipAnimationEnd = false;
            super.cancel();
        }

        @Override
        public void cancel() {
            if (DEBUG)
                Slog.d(TAG, "cancel: mTarget=" + mTarget);
            mSkipAnimationEnd = true;
            super.cancel();
        }

        /**
         * @return true if the animation target is the same as the input bounds.
         */
        boolean isAnimatingTo(Rect bounds) {
            return mTo.equals(bounds);
        }

        /**
         * @return true if we are animating to a larger surface size
         */
        @VisibleForTesting
        boolean animatingToLargerSize() {
            // TODO: Fix this check for aspect ratio changes
            return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height());
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        // Do nothing
        }

        @Override
        public AnimationHandler getAnimationHandler() {
            if (mAnimationHandler != null) {
                return mAnimationHandler;
            }
            return super.getAnimationHandler();
        }
    }

    public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen) {
        animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen);
    }

    @VisibleForTesting
    BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen) {
        final BoundsAnimator existing = mRunningAnimations.get(target);
        final boolean replacing = existing != null;
        @SchedulePipModeChangedState
        int prevSchedulePipModeChangedState = NO_PIP_MODE_CHANGED_CALLBACKS;
        if (DEBUG)
            Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to + " schedulePipModeChangedState=" + schedulePipModeChangedState + " replacing=" + replacing);
        if (replacing) {
            if (existing.isAnimatingTo(to) && (!moveToFullscreen || existing.mMoveToFullscreen) && (!moveFromFullscreen || existing.mMoveFromFullscreen)) {
                // Just let the current animation complete if it has the same destination as the
                // one we are trying to start, and, if moveTo/FromFullscreen was requested, already
                // has that flag set.
                if (DEBUG)
                    Slog.d(TAG, "animateBounds: same destination and moveTo/From flags as " + "existing=" + existing + ", ignoring...");
                return existing;
            }
            // Save the previous state
            prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
            // Update the PiP callback states if we are replacing the animation
            if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    if (DEBUG)
                        Slog.d(TAG, "animateBounds: still animating to fullscreen, keep" + " existing deferred state");
                } else {
                    if (DEBUG)
                        Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback" + " on start already processed, schedule deferred update on end");
                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }
            } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) {
                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    if (DEBUG)
                        Slog.d(TAG, "animateBounds: non-fullscreen animation canceled," + " callback on start will be processed");
                } else {
                    if (DEBUG)
                        Slog.d(TAG, "animateBounds: still animating from fullscreen, keep" + " existing deferred state");
                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }
            }
            // We need to keep the previous moveTo/FromFullscreen flag, unless the new animation
            // specifies a direction.
            if (!moveFromFullscreen && !moveToFullscreen) {
                moveToFullscreen = existing.mMoveToFullscreen;
                moveFromFullscreen = existing.mMoveFromFullscreen;
            }
            // Since we are replacing, we skip both animation start and end callbacks
            existing.cancel();
        }
        final BoundsAnimator animator = new BoundsAnimator(target, from, to, schedulePipModeChangedState, prevSchedulePipModeChangedState, moveFromFullscreen, moveToFullscreen);
        mRunningAnimations.put(target, animator);
        animator.setFloatValues(0f, 1f);
        animator.setDuration((animationDuration != -1 ? animationDuration : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.start();
        return animator;
    }

    public Handler getHandler() {
        return mHandler;
    }

    public void onAllWindowsDrawn() {
        if (DEBUG)
            Slog.d(TAG, "onAllWindowsDrawn:");
        mHandler.post(this::resume);
    }

    private void resume() {
        for (int i = 0; i < mRunningAnimations.size(); i++) {
            final BoundsAnimator b = mRunningAnimations.valueAt(i);
            b.resume();
        }
    }

    private void updateBooster() {
        WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(!mRunningAnimations.isEmpty());
    }
}

16 Source : SurfaceAnimationRunner.java
with Apache License 2.0
from lulululbj

/**
 * Clreplaced to run animations without holding the window manager lock.
 */
clreplaced SurfaceAnimationRunner {

    private final Object mLock = new Object();

    /**
     * Lock for cancelling animations. Must be acquired on it's own, or after acquiring
     * {@link #mLock}
     */
    private final Object mCancelLock = new Object();

    @VisibleForTesting
    Creplacedographer mCreplacedographer;

    private final Runnable mApplyTransactionRunnable = this::applyTransaction;

    private final AnimationHandler mAnimationHandler;

    private final Transaction mFrameTransaction;

    private final AnimatorFactory mAnimatorFactory;

    private final PowerManagerInternal mPowerManagerInternal;

    private boolean mApplyScheduled;

    @GuardedBy("mLock")
    @VisibleForTesting
    final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();

    @GuardedBy("mLock")
    @VisibleForTesting
    final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();

    @GuardedBy("mLock")
    private boolean mAnimationStartDeferred;

    SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
        this(null, /* callbackProvider */
        null, /* animatorFactory */
        new Transaction(), powerManagerInternal);
    }

    @VisibleForTesting
    SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider, AnimatorFactory animatorFactory, Transaction frameTransaction, PowerManagerInternal powerManagerInternal) {
        SurfaceAnimationThread.getHandler().runWithScissors(() -> mCreplacedographer = getSfInstance(), 0);
        mFrameTransaction = frameTransaction;
        mAnimationHandler = new AnimationHandler();
        mAnimationHandler.setProvider(callbackProvider != null ? callbackProvider : new SfVsyncFrameCallbackProvider(mCreplacedographer));
        mAnimatorFactory = animatorFactory != null ? animatorFactory : SfValueAnimator::new;
        mPowerManagerInternal = powerManagerInternal;
    }

    /**
     * Defers starting of animations until {@link #continueStartingAnimations} is called. This
     * method is NOT nestable.
     *
     * @see #continueStartingAnimations
     */
    void deferStartingAnimations() {
        synchronized (mLock) {
            mAnimationStartDeferred = true;
        }
    }

    /**
     * Continues starting of animations.
     *
     * @see #deferStartingAnimations
     */
    void continueStartingAnimations() {
        synchronized (mLock) {
            mAnimationStartDeferred = false;
            if (!mPendingAnimations.isEmpty()) {
                mCreplacedographer.postFrameCallback(this::startAnimations);
            }
        }
    }

    void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t, Runnable finishCallback) {
        synchronized (mLock) {
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash, finishCallback);
            mPendingAnimations.put(animationLeash, runningAnim);
            if (!mAnimationStartDeferred) {
                mCreplacedographer.postFrameCallback(this::startAnimations);
            }
            // Some animations (e.g. move animations) require the initial transform to be applied
            // immediately.
            applyTransformation(runningAnim, t, 0);
        }
    }

    void onAnimationCancelled(SurfaceControl leash) {
        synchronized (mLock) {
            if (mPendingAnimations.containsKey(leash)) {
                mPendingAnimations.remove(leash);
                return;
            }
            final RunningAnimation anim = mRunningAnimations.get(leash);
            if (anim != null) {
                mRunningAnimations.remove(leash);
                synchronized (mCancelLock) {
                    anim.mCancelled = true;
                }
                SurfaceAnimationThread.getHandler().post(() -> {
                    anim.mAnim.cancel();
                    applyTransaction();
                });
            }
        }
    }

    @GuardedBy("mLock")
    private void startPendingAnimationsLocked() {
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
            startAnimationLocked(mPendingAnimations.valueAt(i));
        }
        mPendingAnimations.clear();
    }

    @GuardedBy("mLock")
    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();
        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }
            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();
        });
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        mFrameTransaction.show(a.mLeash);
                    }
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {
                            // Post on other thread that we can push final state without jank.
                            AnimationThread.getHandler().post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);
        anim.start();
        if (a.mAnimSpec.canSkipFirstFrame()) {
            // If we can skip the first frame, we start one frame later.
            anim.setCurrentPlayTime(mCreplacedographer.getFrameIntervalNanos() / NANOS_PER_MS);
        }
        // Immediately start the animation by manually applying an animation frame. Otherwise, the
        // start time would only be set in the next frame, leading to a delay.
        anim.doAnimationFrame(mCreplacedographer.getFrameTime());
    }

    private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
        if (a.mAnimSpec.needsEarlyWakeup()) {
            t.setEarlyWakeup();
        }
        a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
    }

    private void startAnimations(long frameTimeNanos) {
        synchronized (mLock) {
            startPendingAnimationsLocked();
        }
        mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
    }

    private void scheduleApplyTransaction() {
        if (!mApplyScheduled) {
            mCreplacedographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable, null);
            mApplyScheduled = true;
        }
    }

    private void applyTransaction() {
        mFrameTransaction.setAnimationTransaction();
        mFrameTransaction.apply();
        mApplyScheduled = false;
    }

    private static final clreplaced RunningAnimation {

        final AnimationSpec mAnimSpec;

        final SurfaceControl mLeash;

        final Runnable mFinishCallback;

        ValueAnimator mAnim;

        @GuardedBy("mCancelLock")
        private boolean mCancelled;

        RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
            mAnimSpec = animSpec;
            mLeash = leash;
            mFinishCallback = finishCallback;
        }
    }

    @VisibleForTesting
    interface AnimatorFactory {

        ValueAnimator makeAnimator();
    }

    /**
     * Value animator that uses sf-vsync signal to tick.
     */
    private clreplaced SfValueAnimator extends ValueAnimator {

        SfValueAnimator() {
            setFloatValues(0f, 1f);
        }

        @Override
        public AnimationHandler getAnimationHandler() {
            return mAnimationHandler;
        }
    }
}