1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.graphics.drawable;
18
19import android.animation.ValueAnimator;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.res.Resources;
23import android.content.res.Resources.Theme;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Animatable;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.DrawableContainer;
28import android.util.AttributeSet;
29
30import com.android.internal.R;
31
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34
35import java.io.IOException;
36
37/**
38 * An internal DrawableContainer class, used to draw different things depending on animation scale.
39 * i.e: animation scale can be 0 in battery saver mode.
40 * This class contains 2 drawable, one is animatable, the other is static. When animation scale is
41 * not 0, the animatable drawable will the drawn. Otherwise, the static drawable will be drawn.
42 * <p>This class implements Animatable since ProgressBar can pick this up similarly as an
43 * AnimatedVectorDrawable.
44 * <p>It can be defined in an XML file with the {@code <AnimationScaleListDrawable>}
45 * element.
46 */
47public class AnimationScaleListDrawable extends DrawableContainer implements Animatable {
48    private static final String TAG = "AnimationScaleListDrawable";
49    private AnimationScaleListState mAnimationScaleListState;
50    private boolean mMutated;
51
52    public AnimationScaleListDrawable() {
53        this(null, null);
54    }
55
56    private AnimationScaleListDrawable(@Nullable AnimationScaleListState state,
57            @Nullable Resources res) {
58        // Every scale list drawable has its own constant state.
59        final AnimationScaleListState newState = new AnimationScaleListState(state, this, res);
60        setConstantState(newState);
61        onStateChange(getState());
62    }
63
64    /**
65     * Set the current drawable according to the animation scale. If scale is 0, then pick the
66     * static drawable, otherwise, pick the animatable drawable.
67     */
68    @Override
69    protected boolean onStateChange(int[] stateSet) {
70        final boolean changed = super.onStateChange(stateSet);
71        int idx = mAnimationScaleListState.getCurrentDrawableIndexBasedOnScale();
72        return selectDrawable(idx) || changed;
73    }
74
75
76    @Override
77    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
78            @NonNull AttributeSet attrs, @Nullable Theme theme)
79            throws XmlPullParserException, IOException {
80        final TypedArray a = obtainAttributes(r, theme, attrs,
81                R.styleable.AnimationScaleListDrawable);
82        updateDensity(r);
83        a.recycle();
84
85        inflateChildElements(r, parser, attrs, theme);
86
87        onStateChange(getState());
88    }
89
90    /**
91     * Inflates child elements from XML.
92     */
93    private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser,
94            @NonNull AttributeSet attrs, @Nullable Theme theme)
95            throws XmlPullParserException, IOException {
96        final AnimationScaleListState state = mAnimationScaleListState;
97        final int innerDepth = parser.getDepth() + 1;
98        int type;
99        int depth;
100        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
101                && ((depth = parser.getDepth()) >= innerDepth
102                || type != XmlPullParser.END_TAG)) {
103            if (type != XmlPullParser.START_TAG) {
104                continue;
105            }
106
107            if (depth > innerDepth || !parser.getName().equals("item")) {
108                continue;
109            }
110
111            // Either pick up the android:drawable attribute.
112            final TypedArray a = obtainAttributes(r, theme, attrs,
113                    R.styleable.AnimationScaleListDrawableItem);
114            Drawable dr = a.getDrawable(R.styleable.AnimationScaleListDrawableItem_drawable);
115            a.recycle();
116
117            // Or parse the child element under <item>.
118            if (dr == null) {
119                while ((type = parser.next()) == XmlPullParser.TEXT) {
120                }
121                if (type != XmlPullParser.START_TAG) {
122                    throw new XmlPullParserException(
123                            parser.getPositionDescription()
124                                    + ": <item> tag requires a 'drawable' attribute or "
125                                    + "child tag defining a drawable");
126                }
127                dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
128            }
129
130            state.addDrawable(dr);
131        }
132    }
133
134    @Override
135    public Drawable mutate() {
136        if (!mMutated && super.mutate() == this) {
137            mAnimationScaleListState.mutate();
138            mMutated = true;
139        }
140        return this;
141    }
142
143    @Override
144    public void clearMutated() {
145        super.clearMutated();
146        mMutated = false;
147    }
148
149    @Override
150    public void start() {
151        Drawable dr = getCurrent();
152        if (dr != null && dr instanceof Animatable) {
153            ((Animatable) dr).start();
154        }
155    }
156
157    @Override
158    public void stop() {
159        Drawable dr = getCurrent();
160        if (dr != null && dr instanceof Animatable) {
161            ((Animatable) dr).stop();
162        }
163    }
164
165    @Override
166    public boolean isRunning() {
167        boolean result = false;
168        Drawable dr = getCurrent();
169        if (dr != null && dr instanceof Animatable) {
170            result = ((Animatable) dr).isRunning();
171        }
172        return result;
173    }
174
175    static class AnimationScaleListState extends DrawableContainerState {
176        int[] mThemeAttrs = null;
177        // The index of the last static drawable.
178        int mStaticDrawableIndex = -1;
179        // The index of the last animatable drawable.
180        int mAnimatableDrawableIndex = -1;
181
182        AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner,
183                Resources res) {
184            super(orig, owner, res);
185
186            if (orig != null) {
187                // Perform a shallow copy and rely on mutate() to deep-copy.
188                mThemeAttrs = orig.mThemeAttrs;
189
190                mStaticDrawableIndex = orig.mStaticDrawableIndex;
191                mAnimatableDrawableIndex = orig.mAnimatableDrawableIndex;
192            }
193
194        }
195
196        void mutate() {
197            mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
198        }
199
200        /**
201         * Add the drawable into the container.
202         * This class only keep track one animatable drawable, and one static. If there are multiple
203         * defined in the XML, then pick the last one.
204         */
205        int addDrawable(Drawable drawable) {
206            final int pos = addChild(drawable);
207            if (drawable instanceof Animatable) {
208                mAnimatableDrawableIndex = pos;
209            } else {
210                mStaticDrawableIndex = pos;
211            }
212            return pos;
213        }
214
215        @Override
216        public Drawable newDrawable() {
217            return new AnimationScaleListDrawable(this, null);
218        }
219
220        @Override
221        public Drawable newDrawable(Resources res) {
222            return new AnimationScaleListDrawable(this, res);
223        }
224
225        @Override
226        public boolean canApplyTheme() {
227            return mThemeAttrs != null || super.canApplyTheme();
228        }
229
230        public int getCurrentDrawableIndexBasedOnScale() {
231            if (ValueAnimator.getDurationScale() == 0) {
232                return mStaticDrawableIndex;
233            }
234            return mAnimatableDrawableIndex;
235        }
236    }
237
238    @Override
239    public void applyTheme(@NonNull Theme theme) {
240        super.applyTheme(theme);
241
242        onStateChange(getState());
243    }
244
245    @Override
246    protected void setConstantState(@NonNull DrawableContainerState state) {
247        super.setConstantState(state);
248
249        if (state instanceof AnimationScaleListState) {
250            mAnimationScaleListState = (AnimationScaleListState) state;
251        }
252    }
253}
254
255