1 /*
2  * Copyright (C) 2006 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 android.graphics.drawable;
18 
19 import java.io.IOException;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.annotation.NonNull;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.Resources.Theme;
28 import android.util.AttributeSet;
29 
30 /**
31  * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value.
32  * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next
33  * greater or equal value assigned to its max attribute.
34  * A good example use of
35  * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current
36  * battery level.
37  * <p>
38  * It can be defined in an XML file with the <code>&lt;level-list></code> element.
39  * Each Drawable level is defined in a nested <code>&lt;item></code>. For example:
40  * </p>
41  * <pre>
42  * &lt;level-list xmlns:android="http://schemas.android.com/apk/res/android">
43  *  &lt;item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
44  *  &lt;item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
45  *  &lt;item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
46  *  &lt;item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
47  * &lt;/level-list>
48  *</pre>
49  * <p>With this XML saved into the res/drawable/ folder of the project, it can be referenced as
50  * the drawable for an {@link android.widget.ImageView}. The default image is the first in the list.
51  * It can then be changed to one of the other levels with
52  * {@link android.widget.ImageView#setImageLevel(int)}. For more
53  * information, see the guide to <a
54  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
55  *
56  * @attr ref android.R.styleable#LevelListDrawableItem_minLevel
57  * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel
58  * @attr ref android.R.styleable#LevelListDrawableItem_drawable
59  */
60 public class LevelListDrawable extends DrawableContainer {
61     private LevelListState mLevelListState;
62     private boolean mMutated;
63 
LevelListDrawable()64     public LevelListDrawable() {
65         this(null, null);
66     }
67 
addLevel(int low, int high, Drawable drawable)68     public void addLevel(int low, int high, Drawable drawable) {
69         if (drawable != null) {
70             mLevelListState.addLevel(low, high, drawable);
71             // in case the new state matches our current state...
72             onLevelChange(getLevel());
73         }
74     }
75 
76     // overrides from Drawable
77 
78     @Override
onLevelChange(int level)79     protected boolean onLevelChange(int level) {
80         int idx = mLevelListState.indexOfLevel(level);
81         if (selectDrawable(idx)) {
82             return true;
83         }
84         return super.onLevelChange(level);
85     }
86 
87     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)88     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
89             throws XmlPullParserException, IOException {
90         super.inflate(r, parser, attrs, theme);
91         updateDensity(r);
92 
93         inflateChildElements(r, parser, attrs, theme);
94     }
95 
inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)96     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
97             Theme theme) throws XmlPullParserException, IOException {
98         int type;
99 
100         int low = 0;
101 
102         final int innerDepth = parser.getDepth() + 1;
103         int depth;
104         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
105                 && ((depth = parser.getDepth()) >= innerDepth
106                 || type != XmlPullParser.END_TAG)) {
107             if (type != XmlPullParser.START_TAG) {
108                 continue;
109             }
110 
111             if (depth > innerDepth || !parser.getName().equals("item")) {
112                 continue;
113             }
114 
115             TypedArray a = obtainAttributes(r, theme, attrs,
116                     com.android.internal.R.styleable.LevelListDrawableItem);
117 
118             low = a.getInt(
119                     com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0);
120             int high = a.getInt(
121                     com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0);
122             int drawableRes = a.getResourceId(
123                     com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0);
124 
125             a.recycle();
126 
127             if (high < 0) {
128                 throw new XmlPullParserException(parser.getPositionDescription()
129                         + ": <item> tag requires a 'maxLevel' attribute");
130             }
131 
132             Drawable dr;
133             if (drawableRes != 0) {
134                 dr = r.getDrawable(drawableRes, theme);
135             } else {
136                 while ((type = parser.next()) == XmlPullParser.TEXT) {
137                 }
138                 if (type != XmlPullParser.START_TAG) {
139                     throw new XmlPullParserException(
140                             parser.getPositionDescription()
141                                     + ": <item> tag requires a 'drawable' attribute or "
142                                     + "child tag defining a drawable");
143                 }
144                 dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
145             }
146 
147             mLevelListState.addLevel(low, high, dr);
148         }
149 
150         onLevelChange(getLevel());
151     }
152 
153     @Override
mutate()154     public Drawable mutate() {
155         if (!mMutated && super.mutate() == this) {
156             mLevelListState.mutate();
157             mMutated = true;
158         }
159         return this;
160     }
161 
162     @Override
cloneConstantState()163     LevelListState cloneConstantState() {
164         return new LevelListState(mLevelListState, this, null);
165     }
166 
167     /**
168      * @hide
169      */
clearMutated()170     public void clearMutated() {
171         super.clearMutated();
172         mMutated = false;
173     }
174 
175     private final static class LevelListState extends DrawableContainerState {
176         private int[] mLows;
177         private int[] mHighs;
178 
LevelListState(LevelListState orig, LevelListDrawable owner, Resources res)179         LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) {
180             super(orig, owner, res);
181 
182             if (orig != null) {
183                 // Perform a shallow copy and rely on mutate() to deep-copy.
184                 mLows = orig.mLows;
185                 mHighs = orig.mHighs;
186             } else {
187                 mLows = new int[getCapacity()];
188                 mHighs = new int[getCapacity()];
189             }
190         }
191 
mutate()192         private void mutate() {
193             mLows = mLows.clone();
194             mHighs = mHighs.clone();
195         }
196 
addLevel(int low, int high, Drawable drawable)197         public void addLevel(int low, int high, Drawable drawable) {
198             int pos = addChild(drawable);
199             mLows[pos] = low;
200             mHighs[pos] = high;
201         }
202 
indexOfLevel(int level)203         public int indexOfLevel(int level) {
204             final int[] lows = mLows;
205             final int[] highs = mHighs;
206             final int N = getChildCount();
207             for (int i = 0; i < N; i++) {
208                 if (level >= lows[i] && level <= highs[i]) {
209                     return i;
210                 }
211             }
212             return -1;
213         }
214 
215         @Override
newDrawable()216         public Drawable newDrawable() {
217             return new LevelListDrawable(this, null);
218         }
219 
220         @Override
newDrawable(Resources res)221         public Drawable newDrawable(Resources res) {
222             return new LevelListDrawable(this, res);
223         }
224 
225         @Override
growArray(int oldSize, int newSize)226         public void growArray(int oldSize, int newSize) {
227             super.growArray(oldSize, newSize);
228             int[] newInts = new int[newSize];
229             System.arraycopy(mLows, 0, newInts, 0, oldSize);
230             mLows = newInts;
231             newInts = new int[newSize];
232             System.arraycopy(mHighs, 0, newInts, 0, oldSize);
233             mHighs = newInts;
234         }
235     }
236 
237     @Override
setConstantState(@onNull DrawableContainerState state)238     protected void setConstantState(@NonNull DrawableContainerState state) {
239         super.setConstantState(state);
240 
241         if (state instanceof LevelListState) {
242             mLevelListState = (LevelListState) state;
243         }
244     }
245 
LevelListDrawable(LevelListState state, Resources res)246     private LevelListDrawable(LevelListState state, Resources res) {
247         final LevelListState as = new LevelListState(state, this, res);
248         setConstantState(as);
249         onLevelChange(getLevel());
250     }
251 }
252 
253