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

Commit 9ddcef8e authored by Alan Viverette's avatar Alan Viverette Committed by Android Git Automerger
Browse files

am 3c436cb9: Merge "Postpone AnimatedVectorDrawable animator inflation until...

am 3c436cb9: Merge "Postpone AnimatedVectorDrawable animator inflation until applyTheme()" into mnc-dev

* commit '3c436cb9':
  Postpone AnimatedVectorDrawable animator inflation until applyTheme()
parents 8d61ab00 3c436cb9
Loading
Loading
Loading
Loading
+208 −65
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.animation.Animator.AnimatorListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -128,15 +129,27 @@ import java.util.List;
 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
 */
public class AnimatedVectorDrawable extends Drawable implements Animatable {
    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
    private static final String LOGTAG = "AnimatedVectorDrawable";

    private static final String ANIMATED_VECTOR = "animated-vector";
    private static final String TARGET = "target";

    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;

    /** Local, mutable animator set. */
    private final AnimatorSet mAnimatorSet = new AnimatorSet();

    /**
     * The resources against which this drawable was created. Used to attempt
     * to inflate animators if applyTheme() doesn't get called.
     */
    private Resources mRes;

    private AnimatedVectorDrawableState mAnimatedVectorState;

    /** Whether the animator set has been prepared. */
    private boolean mHasAnimatorSet;

    private boolean mMutated;

    public AnimatedVectorDrawable() {
@@ -145,6 +158,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {

    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
        mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
        mRes = res;
    }

    @Override
@@ -162,7 +176,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     */
    public void clearMutated() {
        super.clearMutated();
        if (mAnimatedVectorState.mVectorDrawable != null) {
            mAnimatedVectorState.mVectorDrawable.clearMutated();
        }
        mMutated = false;
    }

@@ -274,6 +290,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
    @Override
    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
        final AnimatedVectorDrawableState state = mAnimatedVectorState;

        int eventType = parser.getEventType();
        float pathErrorScale = 1;
@@ -291,10 +308,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                        vectorDrawable.setAllowCaching(false);
                        vectorDrawable.setCallback(mCallback);
                        pathErrorScale = vectorDrawable.getPixelSize();
                        if (mAnimatedVectorState.mVectorDrawable != null) {
                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
                        if (state.mVectorDrawable != null) {
                            state.mVectorDrawable.setCallback(null);
                        }
                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
                        state.mVectorDrawable = vectorDrawable;
                    }
                    a.recycle();
                } else if (TARGET.equals(tagName)) {
@@ -302,13 +319,21 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                            R.styleable.AnimatedVectorDrawableTarget);
                    final String target = a.getString(
                            R.styleable.AnimatedVectorDrawableTarget_name);

                    int id = a.getResourceId(
                    final int animResId = a.getResourceId(
                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
                    if (id != 0) {
                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id,
                                pathErrorScale);
                        setupAnimatorsForTarget(target, objectAnimator);
                    if (animResId != 0) {
                        if (theme != null) {
                            final Animator objectAnimator = AnimatorInflater.loadAnimator(
                                    res, theme, animResId, pathErrorScale);
                            state.addTargetAnimator(target, objectAnimator);
                        } else {
                            // The animation may be theme-dependent. As a
                            // workaround until Animator has full support for
                            // applyTheme(), postpone loading the animator
                            // until we have a theme in applyTheme().
                            state.addPendingAnimator(animResId, pathErrorScale, target);

                        }
                    }
                    a.recycle();
                }
@@ -316,15 +341,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {

            eventType = parser.next();
        }
        setupAnimatorSet();
    }

    private void setupAnimatorSet() {
        if (mAnimatedVectorState.mTempAnimators != null) {
            mAnimatedVectorState.mAnimatorSet.playTogether(mAnimatedVectorState.mTempAnimators);
            mAnimatedVectorState.mTempAnimators.clear();
            mAnimatedVectorState.mTempAnimators = null;
        }
        // If we don't have any pending animations, we don't need to hold a
        // reference to the resources.
        mRes = state.mPendingAnims == null ? null : res;
    }

    @Override
@@ -341,6 +361,16 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
            vectorDrawable.applyTheme(t);
        }

        if (t != null) {
            mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
        }

        // If we don't have any pending animations, we don't need to hold a
        // reference to the resources.
        if (mAnimatedVectorState.mPendingAnims == null) {
            mRes = null;
        }
    }

    /**
@@ -350,7 +380,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @param listener the listener to be added to the current set of listeners for this animation.
     */
    public void addListener(AnimatorListener listener) {
        mAnimatedVectorState.mAnimatorSet.addListener(listener);
        mAnimatorSet.addListener(listener);
    }

    /**
@@ -360,7 +390,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     *                 animation.
     */
    public void removeListener(AnimatorListener listener) {
        mAnimatedVectorState.mAnimatorSet.removeListener(listener);
        mAnimatorSet.removeListener(listener);
    }

    /**
@@ -370,23 +400,27 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @return List<AnimatorListener> The set of listeners.
     */
    public List<AnimatorListener> getListeners() {
        return mAnimatedVectorState.mAnimatorSet.getListeners();
        return mAnimatorSet.getListeners();
    }

    private static class AnimatedVectorDrawableState extends ConstantState {
        int mChangingConfigurations;
        VectorDrawable mVectorDrawable;
        // Always have a valid animatorSet to handle all the listeners call.
        AnimatorSet mAnimatorSet = new AnimatorSet();
        // When parsing the XML, we build individual animator and store in this array. At the end,
        // we add this array into the mAnimatorSet.
        private ArrayList<Animator> mTempAnimators;

        /** Animators that require a theme before inflation. */
        ArrayList<PendingAnimator> mPendingAnims;

        /** Fully inflated animators awaiting cloning into an AnimatorSet. */
        ArrayList<Animator> mAnimators;

        /** Map of animators to their target object names */
        ArrayMap<Animator, String> mTargetNameMap;

        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
                Callback owner, Resources res) {
            if (copy != null) {
                mChangingConfigurations = copy.mChangingConfigurations;

                if (copy.mVectorDrawable != null) {
                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
                    if (res != null) {
@@ -400,24 +434,17 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
                    mVectorDrawable.setAllowCaching(false);
                }
                if (copy.mAnimatorSet != null) {
                    final int numAnimators = copy.mTargetNameMap.size();
                    // Deep copy a animator set, and then setup the target map again.
                    mAnimatorSet = copy.mAnimatorSet.clone();
                    mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
                    // Since the new AnimatorSet is cloned from the old one, the order must be the
                    // same inside the array.
                    ArrayList<Animator> oldAnim = copy.mAnimatorSet.getChildAnimations();
                    ArrayList<Animator> newAnim = mAnimatorSet.getChildAnimations();

                    for (int i = 0; i < numAnimators; ++i) {
                        // Target name must be the same for new and old
                        String targetName = copy.mTargetNameMap.get(oldAnim.get(i));
                if (copy.mAnimators != null) {
                    mAnimators = new ArrayList<>(copy.mAnimators);
                }

                        Object newTargetObject = mVectorDrawable.getTargetByName(targetName);
                        newAnim.get(i).setTarget(newTargetObject);
                        mTargetNameMap.put(newAnim.get(i), targetName);
                if (copy.mTargetNameMap != null) {
                    mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
                }

                if (copy.mPendingAnims != null) {
                    mPendingAnims = new ArrayList<>(copy.mPendingAnims);
                }
            } else {
                mVectorDrawable = new VectorDrawable();
@@ -427,7 +454,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        @Override
        public boolean canApplyTheme() {
            return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
                    || super.canApplyTheme();
                    || mPendingAnims != null || super.canApplyTheme();
        }

        @Override
@@ -444,44 +471,157 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }

        public void addPendingAnimator(int resId, float pathErrorScale, String target) {
            if (mPendingAnims == null) {
                mPendingAnims = new ArrayList<>(1);
            }
            mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
        }

    private void setupAnimatorsForTarget(String name, Animator animator) {
        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
        animator.setTarget(target);
        if (mAnimatedVectorState.mTempAnimators == null) {
            mAnimatedVectorState.mTempAnimators = new ArrayList<Animator>();
            mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
        public void addTargetAnimator(String targetName, Animator animator) {
            if (mAnimators == null) {
                mAnimators = new ArrayList<>(1);
                mTargetNameMap = new ArrayMap<>(1);
            }
        mAnimatedVectorState.mTempAnimators.add(animator);
        mAnimatedVectorState.mTargetNameMap.put(animator, name);
            mAnimators.add(animator);
            mTargetNameMap.put(animator, targetName);

            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
                Log.v(LOGTAG, "add animator  for target " + targetName + " " + animator);
            }
        }

        /**
         * Prepares a local set of mutable animators based on the constant
         * state.
         * <p>
         * If there are any pending uninflated animators, attempts to inflate
         * them immediately against the provided resources object.
         *
         * @param animatorSet the animator set to which the animators should
         *                    be added
         * @param res the resources against which to inflate any pending
         *            animators, or {@code null} if not available
         */
        public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
                @Nullable Resources res) {
            // Check for uninflated animators. We can remove this after we add
            // support for Animator.applyTheme(). See comments in inflate().
            if (mPendingAnims != null) {
                // Attempt to load animators without applying a theme.
                if (res != null) {
                    inflatePendingAnimators(res, null);
                } else {
                    Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
                            + " must be created using a Resources object or applyTheme() must be"
                            + " called with a non-null Theme object.");
                }

                mPendingAnims = null;
            }

            // Perform a deep copy of the constant state's animators.
            final int count = mAnimators == null ? 0 : mAnimators.size();
            if (count > 0) {
                final Animator firstAnim = prepareLocalAnimator(0);
                final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
                for (int i = 1; i < count; ++i) {
                    final Animator nextAnim = prepareLocalAnimator(i);
                    builder.with(nextAnim);
                }
            }
        }

        /**
         * Prepares a local animator for the given index within the constant
         * state's list of animators.
         *
         * @param index the index of the animator within the constant state
         */
        private Animator prepareLocalAnimator(int index) {
            final Animator animator = mAnimators.get(index);
            final Animator localAnimator = animator.clone();
            final String targetName = mTargetNameMap.get(animator);
            final Object target = mVectorDrawable.getTargetByName(targetName);
            localAnimator.setTarget(target);
            return localAnimator;
        }

        /**
         * Inflates pending animators, if any, against a theme. Clears the list of
         * pending animators.
         *
         * @param t the theme against which to inflate the animators
         */
        public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
            final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
            if (pendingAnims != null) {
                mPendingAnims = null;

                for (int i = 0, count = pendingAnims.size(); i < count; i++) {
                    final PendingAnimator pendingAnimator = pendingAnims.get(i);
                    final Animator objectAnimator = pendingAnimator.newInstance(res, t);
                    addTargetAnimator(pendingAnimator.target, objectAnimator);
                }
            }
        }

        /**
         * Basically a constant state for Animators until we actually implement
         * constant states for Animators.
         */
        private static class PendingAnimator {
            public final int animResId;
            public final float pathErrorScale;
            public final String target;

            public PendingAnimator(int animResId, float pathErrorScale, String target) {
                this.animResId = animResId;
                this.pathErrorScale = pathErrorScale;
                this.target = target;
            }

            public Animator newInstance(Resources res, Theme theme) {
                return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
            }
        }
    }

    @Override
    public boolean isRunning() {
        return mAnimatedVectorState.mAnimatorSet.isRunning();
        return mAnimatorSet.isRunning();
    }

    private boolean isStarted() {
        return mAnimatedVectorState.mAnimatorSet.isStarted();
        return mAnimatorSet.isStarted();
    }

    @Override
    public void start() {
        ensureAnimatorSet();

        // If any one of the animator has not ended, do nothing.
        if (isStarted()) {
            return;
        }
        mAnimatedVectorState.mAnimatorSet.start();

        mAnimatorSet.start();
        invalidateSelf();
    }

    @NonNull
    private void ensureAnimatorSet() {
        if (!mHasAnimatorSet) {
            mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes);
            mHasAnimatorSet = true;
            mRes = null;
        }
    }

    @Override
    public void stop() {
        mAnimatedVectorState.mAnimatorSet.end();
        mAnimatorSet.end();
    }

    /**
@@ -492,20 +632,23 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @hide
     */
    public void reverse() {
        // Only reverse when all the animators can be reverse. Otherwise, partially
        // reverse is confusing.
        ensureAnimatorSet();

        // Only reverse when all the animators can be reversed.
        if (!canReverse()) {
            Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
            return;
        }
        mAnimatedVectorState.mAnimatorSet.reverse();

        mAnimatorSet.reverse();
        invalidateSelf();
    }

    /**
     * @hide
     */
    public boolean canReverse() {
        return mAnimatedVectorState.mAnimatorSet.canReverse();
        return mAnimatorSet.canReverse();
    }

    private final Callback mCallback = new Callback() {