1 /*
2  * Copyright (C) 2014 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 junit.framework.Assert.fail;
20 import static junit.framework.TestCase.assertFalse;
21 import static junit.framework.TestCase.assertTrue;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertNotNull;
25 
26 import android.Manifest;
27 import android.app.Activity;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.Canvas;
31 import android.graphics.Color;
32 import android.graphics.Insets;
33 import android.graphics.PixelFormat;
34 import android.graphics.PorterDuff;
35 import android.graphics.PorterDuffColorFilter;
36 import android.graphics.cts.R;
37 import android.graphics.drawable.AnimatedVectorDrawable;
38 import android.graphics.drawable.Drawable.ConstantState;
39 import android.util.AttributeSet;
40 import android.util.Xml;
41 import android.widget.ImageView;
42 
43 import androidx.test.filters.FlakyTest;
44 import androidx.test.filters.LargeTest;
45 import androidx.test.filters.SmallTest;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 import androidx.test.rule.ActivityTestRule;
48 import androidx.test.runner.AndroidJUnit4;
49 
50 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
51 
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.xmlpull.v1.XmlPullParser;
57 import org.xmlpull.v1.XmlPullParserException;
58 
59 @LargeTest
60 @RunWith(AndroidJUnit4.class)
61 public class AnimatedVectorDrawableTest {
62     private static final int IMAGE_WIDTH = 64;
63     private static final int IMAGE_HEIGHT = 64;
64     private static final long MAX_TIMEOUT_MS = 1000;
65     private static final int MS_TO_NS = 1000000;
66 
67     @Rule(order = 0)
68     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
69             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
70             Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
71 
72     @Rule(order = 1)
73     public ActivityTestRule<DrawableStubActivity> mActivityRule =
74             new ActivityTestRule<DrawableStubActivity>(DrawableStubActivity.class);
75     private Activity mActivity;
76     private Resources mResources;
77     private static final boolean DBG_DUMP_PNG = false;
78     private final int mResId = R.drawable.animation_vector_drawable_grouping_1;
79     private final int mLayoutId = R.layout.animated_vector_drawable_source;
80     private final int mImageViewId = R.id.avd_view;
81 
82     @Before
setup()83     public void setup() {
84         mActivity = mActivityRule.getActivity();
85         mResources = mActivity.getResources();
86     }
87 
88     @SmallTest
89     @Test
testInflate()90     public void testInflate() throws Exception {
91         // Setup AnimatedVectorDrawable from xml file
92         XmlPullParser parser = mResources.getXml(mResId);
93         AttributeSet attrs = Xml.asAttributeSet(parser);
94 
95         int type;
96         while ((type=parser.next()) != XmlPullParser.START_TAG &&
97                 type != XmlPullParser.END_DOCUMENT) {
98             // Empty loop
99         }
100 
101         if (type != XmlPullParser.START_TAG) {
102             throw new XmlPullParserException("No start tag found");
103         }
104         Bitmap bitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888);
105         Canvas canvas = new Canvas(bitmap);
106         AnimatedVectorDrawable drawable = new AnimatedVectorDrawable();
107         drawable.inflate(mResources, parser, attrs);
108         drawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
109         bitmap.eraseColor(0);
110         drawable.draw(canvas);
111         int sunColor = bitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
112         int earthColor = bitmap.getPixel(IMAGE_WIDTH * 3 / 4 + 2, IMAGE_HEIGHT / 2);
113         assertTrue(sunColor == 0xFFFF8000);
114         assertTrue(earthColor == 0xFF5656EA);
115 
116         if (DBG_DUMP_PNG) {
117             DrawableTestUtils.saveAutoNamedVectorDrawableIntoPNG(mActivity, bitmap, mResId, null);
118         }
119     }
120 
121     @SmallTest
122     @Test
testGetOpticalInsets()123     public void testGetOpticalInsets() throws Exception {
124         XmlPullParser parser = mResources.getXml(mResId);
125         AttributeSet attrs = Xml.asAttributeSet(parser);
126         AnimatedVectorDrawable drawable = new AnimatedVectorDrawable();
127         drawable.inflate(mResources, parser, attrs);
128 
129         assertEquals(Insets.of(10, 20, 30, 40), drawable.getOpticalInsets());
130     }
131 
132     @Test
testGetChangingConfigurations()133     public void testGetChangingConfigurations() {
134         AnimatedVectorDrawable avd = new AnimatedVectorDrawable();
135         ConstantState constantState = avd.getConstantState();
136 
137         // default
138         assertEquals(0, constantState.getChangingConfigurations());
139         assertEquals(0, avd.getChangingConfigurations());
140 
141         // change the drawable's configuration does not affect the state's configuration
142         avd.setChangingConfigurations(0xff);
143         assertEquals(0xff, avd.getChangingConfigurations());
144         assertEquals(0, constantState.getChangingConfigurations());
145 
146         // the state's configuration get refreshed
147         constantState = avd.getConstantState();
148         assertEquals(0xff,  constantState.getChangingConfigurations());
149 
150         // set a new configuration to drawable
151         avd.setChangingConfigurations(0xff00);
152         assertEquals(0xff,  constantState.getChangingConfigurations());
153         assertEquals(0xffff,  avd.getChangingConfigurations());
154     }
155 
156     @Test
testGetConstantState()157     public void testGetConstantState() {
158         AnimatedVectorDrawable AnimatedVectorDrawable = new AnimatedVectorDrawable();
159         ConstantState constantState = AnimatedVectorDrawable.getConstantState();
160         assertNotNull(constantState);
161         assertEquals(0, constantState.getChangingConfigurations());
162 
163         AnimatedVectorDrawable.setChangingConfigurations(1);
164         constantState = AnimatedVectorDrawable.getConstantState();
165         assertNotNull(constantState);
166         assertEquals(1, constantState.getChangingConfigurations());
167     }
168 
169     @SmallTest
170     @Test
testMutate()171     public void testMutate() {
172         AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
173         AnimatedVectorDrawable d2 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
174         AnimatedVectorDrawable d3 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
175         int restoreAlpha = d1.getAlpha();
176 
177         try {
178             int originalAlpha = d2.getAlpha();
179             int newAlpha = (originalAlpha + 1) % 255;
180 
181             // AVD is different than VectorDrawable. Every instance of it is a deep copy
182             // of the VectorDrawable.
183             // So every setAlpha operation will happen only to that specific object.
184             d1.setAlpha(newAlpha);
185             assertEquals(newAlpha, d1.getAlpha());
186             assertEquals(originalAlpha, d2.getAlpha());
187             assertEquals(originalAlpha, d3.getAlpha());
188 
189             d1.mutate();
190             d1.setAlpha(0x40);
191             assertEquals(0x40, d1.getAlpha());
192             assertEquals(originalAlpha, d2.getAlpha());
193             assertEquals(originalAlpha, d3.getAlpha());
194 
195             d2.setAlpha(0x20);
196             assertEquals(0x40, d1.getAlpha());
197             assertEquals(0x20, d2.getAlpha());
198             assertEquals(originalAlpha, d3.getAlpha());
199         } finally {
200             mResources.getDrawable(mResId).setAlpha(restoreAlpha);
201         }
202     }
203 
204     @SmallTest
205     @Test
testGetOpacity()206     public void testGetOpacity() {
207         AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
208         assertEquals("Default is translucent", PixelFormat.TRANSLUCENT, d1.getOpacity());
209         d1.setAlpha(0);
210         assertEquals("Still translucent", PixelFormat.TRANSLUCENT, d1.getOpacity());
211     }
212 
213     @SmallTest
214     @Test
testColorFilter()215     public void testColorFilter() {
216         PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
217         AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
218         d1.setColorFilter(filter);
219 
220         assertEquals(filter, d1.getColorFilter());
221     }
222 
223     @Test
testReset()224     public void testReset() throws Throwable {
225         final Animatable2Callback callback = new Animatable2Callback();
226         final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
227         // The AVD has a duration as 100ms.
228         mActivityRule.runOnUiThread(() -> {
229             d1.registerAnimationCallback(callback);
230             d1.start();
231             d1.reset();
232         });
233         waitForAVDStop(callback, MAX_TIMEOUT_MS);
234         assertFalse(d1.isRunning());
235 
236     }
237 
238     @Test
testStop()239     public void testStop() throws Throwable {
240         final Animatable2Callback callback = new Animatable2Callback();
241         final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
242         // The AVD has a duration as 100ms.
243         mActivityRule.runOnUiThread(() -> {
244             d1.registerAnimationCallback(callback);
245             d1.start();
246             d1.stop();
247         });
248         waitForAVDStop(callback, MAX_TIMEOUT_MS);
249         assertFalse(d1.isRunning());
250     }
251 
252     @Test
253     @FlakyTest (bugId = 72737527)
testAddCallbackBeforeStart()254     public void testAddCallbackBeforeStart() throws Throwable {
255         final Animatable2Callback callback = new Animatable2Callback();
256         // The AVD has a duration as 100ms.
257         mActivityRule.runOnUiThread(() -> {
258             mActivity.setContentView(mLayoutId);
259             ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
260             AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable();
261             d1.registerAnimationCallback(callback);
262             d1.start();
263         });
264         callback.waitForStart();
265         waitForAVDStop(callback, MAX_TIMEOUT_MS);
266         callback.assertStarted(true);
267         callback.assertEnded(true);
268     }
269 
270     @Test
271     @FlakyTest (bugId = 72737527)
testAddCallbackAfterTrigger()272     public void testAddCallbackAfterTrigger() throws Throwable {
273         final Animatable2Callback callback = new Animatable2Callback();
274         // The AVD has a duration as 100ms.
275         mActivityRule.runOnUiThread(() -> {
276             mActivity.setContentView(mLayoutId);
277             ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
278             AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable();
279             // This reset call can enforce the AnimatorSet is setup properly in AVD, when
280             // running on UI thread.
281             d1.reset();
282             d1.registerAnimationCallback(callback);
283             d1.start();
284         });
285         callback.waitForStart();
286         waitForAVDStop(callback, MAX_TIMEOUT_MS);
287 
288         callback.assertStarted(true);
289         callback.assertEnded(true);
290     }
291 
292     @Test
293     @FlakyTest (bugId = 72737527)
testAddCallbackAfterStart()294     public void testAddCallbackAfterStart() throws Throwable {
295         final Animatable2Callback callback = new Animatable2Callback();
296         // The AVD has a duration as 100ms.
297         mActivityRule.runOnUiThread(() -> {
298             mActivity.setContentView(mLayoutId);
299             ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
300             AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable();
301             d1.start();
302             d1.registerAnimationCallback(callback);
303         });
304         callback.waitForStart();
305 
306         waitForAVDStop(callback, MAX_TIMEOUT_MS);
307         // Whether or not the callback.start is true could vary when running on Render Thread.
308         // Therefore, we don't make assertion here. The most useful flag is the callback.mEnded.
309         callback.assertEnded(true);
310         callback.assertAVDRuntime(0, 400 * MS_TO_NS); // 4 times of the duration of the AVD.
311     }
312 
313     @Test
testRemoveCallback()314     public void testRemoveCallback() throws Throwable {
315         final Animatable2Callback callback = new Animatable2Callback();
316         // The AVD has a duration as 100ms.
317         mActivityRule.runOnUiThread(() -> {
318             mActivity.setContentView(mLayoutId);
319             ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
320             AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable();
321             d1.registerAnimationCallback(callback);
322             assertTrue(d1.unregisterAnimationCallback(callback));
323             d1.start();
324         });
325         callback.waitForStart();
326 
327         waitForAVDStop(callback, MAX_TIMEOUT_MS);
328         callback.assertStarted(false);
329         callback.assertEnded(false);
330     }
331 
332     @Test
333     @FlakyTest (bugId = 72737527)
testClearCallback()334     public void testClearCallback() throws Throwable {
335         final Animatable2Callback callback = new Animatable2Callback();
336 
337         // The AVD has a duration as 100ms.
338         mActivityRule.runOnUiThread(() -> {
339             mActivity.setContentView(mLayoutId);
340             ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
341             AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) imageView.getDrawable();
342             d1.registerAnimationCallback(callback);
343             d1.clearAnimationCallbacks();
344             d1.start();
345         });
346         callback.waitForStart();
347 
348         waitForAVDStop(callback, MAX_TIMEOUT_MS);
349         callback.assertStarted(false);
350         callback.assertEnded(false);
351     }
352 
353     // The time out is expected when the listener is removed successfully.
354     // Such that we don't get the end event.
waitForAVDStop(Animatable2Callback callback, long timeout)355     static void waitForAVDStop(Animatable2Callback callback, long timeout) {
356         try {
357             callback.waitForEnd(timeout);
358         } catch (InterruptedException e) {
359             e.printStackTrace();
360             fail("We should not see the AVD run this long time!");
361         }
362     }
363 }
364