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 android.graphics.drawable.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.mockito.Mockito.mock;
27 
28 import android.content.Context;
29 import android.content.res.Resources;
30 import android.content.res.XmlResourceParser;
31 import android.graphics.Canvas;
32 import android.graphics.ColorFilter;
33 import android.graphics.cts.R;
34 import android.graphics.drawable.Animatable;
35 import android.graphics.drawable.Animatable2;
36 import android.graphics.drawable.AnimatedStateListDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.graphics.drawable.DrawableContainer.DrawableContainerState;
39 import android.graphics.drawable.StateListDrawable;
40 import android.util.StateSet;
41 import android.util.Xml;
42 
43 import androidx.test.InstrumentationRegistry;
44 import androidx.test.filters.SmallTest;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.xmlpull.v1.XmlPullParser;
51 import org.xmlpull.v1.XmlPullParserException;
52 
53 import java.io.IOException;
54 import java.util.Arrays;
55 import java.util.HashSet;
56 
57 @SmallTest
58 @RunWith(AndroidJUnit4.class)
59 public class AnimatedStateListDrawableTest {
60     private static final int[] STATE_EMPTY = new int[] { };
61     private static final int[] STATE_FOCUSED = new int[] { android.R.attr.state_focused };
62 
63     private Context mContext;
64     private Resources mResources;
65 
66     @Before
setup()67     public void setup() {
68         mContext = InstrumentationRegistry.getTargetContext();
69         mResources = mContext.getResources();
70     }
71 
72     @Test
testStateListDrawable()73     public void testStateListDrawable() {
74         new AnimatedStateListDrawable();
75 
76         // Check the values set in the constructor
77         assertNotNull(new AnimatedStateListDrawable().getConstantState());
78     }
79 
80     @Test
testAddState()81     public void testAddState() {
82         AnimatedStateListDrawable asld = new AnimatedStateListDrawable();
83         DrawableContainerState cs = (DrawableContainerState) asld.getConstantState();
84         assertEquals(0, cs.getChildCount());
85 
86         try {
87             asld.addState(StateSet.WILD_CARD, null, R.id.focused);
88             fail("Expected IllegalArgumentException");
89         } catch (IllegalArgumentException e) {
90             // Expected.
91         }
92 
93         Drawable unfocused = mock(Drawable.class);
94         asld.addState(StateSet.WILD_CARD, unfocused, R.id.focused);
95         assertEquals(1, cs.getChildCount());
96 
97         Drawable focused = mock(Drawable.class);
98         asld.addState(STATE_FOCUSED, focused, R.id.unfocused);
99         assertEquals(2, cs.getChildCount());
100     }
101 
102     @Test
testAddTransition()103     public void testAddTransition() {
104         AnimatedStateListDrawable asld = new AnimatedStateListDrawable();
105         DrawableContainerState cs = (DrawableContainerState) asld.getConstantState();
106 
107         Drawable focused = mock(Drawable.class);
108         Drawable unfocused = mock(Drawable.class);
109         asld.addState(STATE_FOCUSED, focused, R.id.focused);
110         asld.addState(StateSet.WILD_CARD, unfocused, R.id.unfocused);
111 
112         try {
113             asld.addTransition(R.id.focused, R.id.focused, null, false);
114             fail("Expected IllegalArgumentException");
115         } catch (IllegalArgumentException e) {
116             // Expected.
117         }
118 
119         MockTransition focusedToUnfocused = mock(MockTransition.class);
120         asld.addTransition(R.id.focused, R.id.unfocused, focusedToUnfocused, false);
121         assertEquals(3, cs.getChildCount());
122 
123         MockTransition unfocusedToFocused = mock(MockTransition.class);
124         asld.addTransition(R.id.unfocused, R.id.focused, unfocusedToFocused, false);
125         assertEquals(4, cs.getChildCount());
126 
127         MockTransition reversible = mock(MockTransition.class);
128         asld.addTransition(R.id.focused, R.id.unfocused, reversible, true);
129         assertEquals(5, cs.getChildCount());
130     }
131 
132     @Test
testIsStateful()133     public void testIsStateful() {
134         assertTrue(new AnimatedStateListDrawable().isStateful());
135     }
136 
137     @Test
testOnStateChange()138     public void testOnStateChange() {
139         AnimatedStateListDrawable asld = new AnimatedStateListDrawable();
140 
141         Drawable focused = mock(Drawable.class);
142         Drawable unfocused = mock(Drawable.class);
143         asld.addState(STATE_FOCUSED, focused, R.id.focused);
144         asld.addState(StateSet.WILD_CARD, unfocused, R.id.unfocused);
145 
146         MockTransition focusedToUnfocused = mock(MockTransition.class);
147         MockTransition unfocusedToFocused = mock(MockTransition.class);
148         asld.addTransition(R.id.focused, R.id.unfocused, focusedToUnfocused, false);
149         asld.addTransition(R.id.unfocused, R.id.focused, unfocusedToFocused, false);
150 
151         asld.setState(STATE_EMPTY);
152         assertSame(unfocused, asld.getCurrent());
153 
154         asld.setState(STATE_FOCUSED);
155         assertSame(unfocusedToFocused, asld.getCurrent());
156 
157         asld.setState(STATE_FOCUSED);
158         assertSame(unfocusedToFocused, asld.getCurrent());
159     }
160 
161     @Test
testPreloadDensity()162     public void testPreloadDensity() throws XmlPullParserException, IOException {
163         runPreloadDensityTestForDrawable(
164                 R.drawable.animated_state_list_density, false);
165     }
166 
167     @Test
testPreloadDensityConstantSize()168     public void testPreloadDensityConstantSize() throws XmlPullParserException, IOException {
169         runPreloadDensityTestForDrawable(
170                 R.drawable.animated_state_list_density_constant_size, true);
171     }
172 
runPreloadDensityTestForDrawable(int drawableResId, boolean isConstantSize)173     private void runPreloadDensityTestForDrawable(int drawableResId, boolean isConstantSize)
174             throws XmlPullParserException, IOException {
175         final Resources res = mResources;
176         final int densityDpi = res.getConfiguration().densityDpi;
177         try {
178             runPreloadDensityTestForDrawableInner(res, densityDpi, drawableResId, isConstantSize);
179         } finally {
180             DrawableTestUtils.setResourcesDensity(res, densityDpi);
181         }
182     }
183 
runPreloadDensityTestForDrawableInner(Resources res, int densityDpi, int drawableResId, boolean isConstantSize)184     private void runPreloadDensityTestForDrawableInner(Resources res, int densityDpi,
185             int drawableResId, boolean isConstantSize) throws XmlPullParserException, IOException {
186         // Capture initial state at default density.
187         final XmlResourceParser parser = getResourceParser(drawableResId);
188         final AnimatedStateListDrawable asld = new AnimatedStateListDrawable();
189         asld.inflate(res, parser, Xml.asAttributeSet(parser));
190 
191         final DrawableContainerState cs = (DrawableContainerState) asld.getConstantState();
192         final int count = cs.getChildCount();
193         final int[] origWidth = new int[count];
194         int max = 0;
195         for (int i = 0; i < count; i++) {
196             final int width = cs.getChild(i).getIntrinsicWidth();
197             max = Math.max(max, width);
198             origWidth[i] = width;
199         }
200         if (isConstantSize) {
201             Arrays.fill(origWidth, max);
202         }
203 
204         // NOTE: instrinsic W/H may already be a scaled and truncated value computed from the
205         // underlying asset (e.g. bitmap) so account for this previously applied scaling/truncation
206         // by applying a small delta to the asserts.
207 
208         // Set density to half of original.
209         DrawableTestUtils.setResourcesDensity(res, densityDpi / 2);
210         final StateListDrawable halfDrawable =
211                 (StateListDrawable) cs.newDrawable(res);
212         for (int i = 0; i < count; i++) {
213             halfDrawable.selectDrawable(i);
214             assertEquals(Math.round(origWidth[i] * 0.5f), halfDrawable.getIntrinsicWidth(), 1);
215         }
216 
217         // Set density to double original.
218         DrawableTestUtils.setResourcesDensity(res, densityDpi * 2);
219         final StateListDrawable doubleDrawable =
220                 (StateListDrawable) cs.newDrawable(res);
221         for (int i = 0; i < count; i++) {
222             doubleDrawable.selectDrawable(i);
223             assertEquals(origWidth[i] * 2, doubleDrawable.getIntrinsicWidth(), 1);
224         }
225 
226         // Restore original configuration and metrics.
227         DrawableTestUtils.setResourcesDensity(res, densityDpi);
228         final StateListDrawable origDrawable =
229                 (StateListDrawable) cs.newDrawable(res);
230         for (int i = 0; i < count; i++) {
231             origDrawable.selectDrawable(i);
232             assertEquals(origWidth[i], origDrawable.getIntrinsicWidth());
233         }
234     }
235 
getResourceParser(int resId)236     private XmlResourceParser getResourceParser(int resId) throws XmlPullParserException,
237             IOException {
238         XmlResourceParser parser = mResources.getXml(resId);
239         int type;
240         while ((type = parser.next()) != XmlPullParser.START_TAG
241                 && type != XmlPullParser.END_DOCUMENT) {
242             // Empty loop
243         }
244         return parser;
245     }
246 
247     @Test
testInflate()248     public void testInflate() throws XmlPullParserException, IOException {
249         AnimatedStateListDrawable asld = (AnimatedStateListDrawable) mContext.getDrawable(
250                 R.drawable.animated_state_list_density);
251         DrawableContainerState asldState = (DrawableContainerState) asld.getConstantState();
252         assertTrue(asld.isVisible());
253         assertFalse(asldState.isConstantSize());
254         assertNull(asldState.getConstantPadding());
255         assertEquals(4, asldState.getChildCount());
256     }
257 
258     @Test
testParsingTransitionDefinedWithAVD()259     public void testParsingTransitionDefinedWithAVD() {
260         AnimatedStateListDrawable asld = (AnimatedStateListDrawable) mContext.getDrawable(
261                 R.drawable.animated_state_list_with_avd);
262         DrawableContainerState asldState = (DrawableContainerState) asld.getConstantState();
263         // Ensure that everything defined in xml after the definition of a transition with AVD is
264         // parsed by checking the total drawables parsed.
265         assertEquals(6, asldState.getChildCount());
266     }
267 
268     public abstract class MockTransition extends MockDrawable implements Animatable, Animatable2 {
269         private HashSet<AnimationCallback> mCallbacks = new HashSet<>();
270 
271         @Override
registerAnimationCallback(AnimationCallback callback)272         public void registerAnimationCallback(AnimationCallback callback) {
273             mCallbacks.add(callback);
274         }
275 
276         @Override
unregisterAnimationCallback(AnimationCallback callback)277         public boolean unregisterAnimationCallback(AnimationCallback callback) {
278             return mCallbacks.remove(callback);
279         }
280 
281         @Override
clearAnimationCallbacks()282         public void clearAnimationCallbacks() {
283             mCallbacks.clear();
284         }
285     }
286 
287     public class MockDrawable extends Drawable {
288         @Override
draw(Canvas canvas)289         public void draw(Canvas canvas) {
290         }
291 
292         @Override
getOpacity()293         public int getOpacity() {
294             return 0;
295         }
296 
297         @Override
setAlpha(int alpha)298         public void setAlpha(int alpha) {
299         }
300 
301         @Override
setColorFilter(ColorFilter cf)302         public void setColorFilter(ColorFilter cf) {
303         }
304     }
305 }
306