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 
17 package com.android.internal.graphics.drawable;
18 
19 import android.animation.ValueAnimator;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.res.Resources;
23 import android.content.res.Resources.Theme;
24 import android.content.res.TypedArray;
25 import android.graphics.drawable.Animatable;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.DrawableContainer;
28 import android.util.AttributeSet;
29 
30 import com.android.internal.R;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import 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  */
47 public class AnimationScaleListDrawable extends DrawableContainer implements Animatable {
48     private static final String TAG = "AnimationScaleListDrawable";
49     private AnimationScaleListState mAnimationScaleListState;
50     private boolean mMutated;
51 
AnimationScaleListDrawable()52     public AnimationScaleListDrawable() {
53         this(null, null);
54     }
55 
AnimationScaleListDrawable(@ullable AnimationScaleListState state, @Nullable Resources res)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
onStateChange(int[] stateSet)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
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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      */
inflateChildElements(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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
mutate()135     public Drawable mutate() {
136         if (!mMutated && super.mutate() == this) {
137             mAnimationScaleListState.mutate();
138             mMutated = true;
139         }
140         return this;
141     }
142 
143     @Override
clearMutated()144     public void clearMutated() {
145         super.clearMutated();
146         mMutated = false;
147     }
148 
149     @Override
start()150     public void start() {
151         Drawable dr = getCurrent();
152         if (dr != null && dr instanceof Animatable) {
153             ((Animatable) dr).start();
154         }
155     }
156 
157     @Override
stop()158     public void stop() {
159         Drawable dr = getCurrent();
160         if (dr != null && dr instanceof Animatable) {
161             ((Animatable) dr).stop();
162         }
163     }
164 
165     @Override
isRunning()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 
AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner, Resources res)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 
mutate()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          */
addDrawable(Drawable drawable)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
newDrawable()216         public Drawable newDrawable() {
217             return new AnimationScaleListDrawable(this, null);
218         }
219 
220         @Override
newDrawable(Resources res)221         public Drawable newDrawable(Resources res) {
222             return new AnimationScaleListDrawable(this, res);
223         }
224 
225         @Override
canApplyTheme()226         public boolean canApplyTheme() {
227             return mThemeAttrs != null || super.canApplyTheme();
228         }
229 
getCurrentDrawableIndexBasedOnScale()230         public int getCurrentDrawableIndexBasedOnScale() {
231             if (ValueAnimator.getDurationScale() == 0) {
232                 return mStaticDrawableIndex;
233             }
234             return mAnimatableDrawableIndex;
235         }
236     }
237 
238     @Override
applyTheme(@onNull Theme theme)239     public void applyTheme(@NonNull Theme theme) {
240         super.applyTheme(theme);
241 
242         onStateChange(getState());
243     }
244 
245     @Override
setConstantState(@onNull DrawableContainerState state)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