• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.assertNotEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.Manifest;
27 import android.app.Activity;
28 import android.content.ContentResolver;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Color;
33 import android.graphics.ColorFilter;
34 import android.graphics.ImageDecoder;
35 import android.graphics.LightingColorFilter;
36 import android.graphics.Paint;
37 import android.graphics.PixelFormat;
38 import android.graphics.Rect;
39 import android.graphics.cts.R;
40 import android.graphics.cts.Utils;
41 import android.graphics.drawable.AnimatedImageDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.net.Uri;
44 import android.platform.test.annotations.RequiresFlagsEnabled;
45 import android.platform.test.flag.junit.CheckFlagsRule;
46 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
47 import android.view.View;
48 import android.widget.ImageView;
49 
50 import androidx.test.InstrumentationRegistry;
51 import androidx.test.filters.FlakyTest;
52 import androidx.test.rule.ActivityTestRule;
53 
54 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
55 import com.android.compatibility.common.util.BitmapUtils;
56 import com.android.compatibility.common.util.WidgetTestUtils;
57 import com.android.compatibility.common.util.WindowUtil;
58 import com.android.graphics.hwui.flags.Flags;
59 
60 import junitparams.JUnitParamsRunner;
61 import junitparams.Parameters;
62 
63 import org.junit.Rule;
64 import org.junit.Test;
65 import org.junit.runner.RunWith;
66 import org.xmlpull.v1.XmlPullParser;
67 import org.xmlpull.v1.XmlPullParserException;
68 
69 import java.io.ByteArrayOutputStream;
70 import java.io.IOException;
71 import java.io.InputStream;
72 import java.nio.ByteBuffer;
73 import java.util.function.BiFunction;
74 
75 @RunWith(JUnitParamsRunner.class)
76 public class AnimatedImageDrawableTest {
77     private ImageView mImageView;
78 
79     private static final int RES_ID = R.drawable.animated;
80     private static final int WIDTH = 278;
81     private static final int HEIGHT = 183;
82     private static final int NUM_FRAMES = 4;
83     private static final int FRAME_DURATION = 250; // in milliseconds
84     private static final int DURATION = NUM_FRAMES * FRAME_DURATION;
85 
86     @Rule(order = 0)
87     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
88             androidx.test.platform.app.InstrumentationRegistry
89                     .getInstrumentation().getUiAutomation(),
90             Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
91 
92     @Rule(order = 1)
93     public ActivityTestRule<AnimatedImageActivity> mActivityRule =
94             new ActivityTestRule<AnimatedImageActivity>(AnimatedImageActivity.class);
95     private Activity mActivity;
96 
97     @Rule(order = 2)
98     public final CheckFlagsRule mCheckFlagsRule =
99             DeviceFlagsValueProvider.createCheckFlagsRule();
100 
getResources()101     private Resources getResources() {
102         return InstrumentationRegistry.getTargetContext().getResources();
103     }
104 
getContentResolver()105     private ContentResolver getContentResolver() {
106         return InstrumentationRegistry.getTargetContext().getContentResolver();
107     }
108 
setupActivity()109     private void setupActivity() {
110         mActivity = mActivityRule.getActivity();
111         WindowUtil.waitForFocus(mActivity);
112         mImageView = mActivity.findViewById(R.id.animated_image);
113     }
114 
115     @Test
testEmptyConstructor()116     public void testEmptyConstructor() {
117         new AnimatedImageDrawable();
118     }
119 
120     @Test
testMutate()121     public void testMutate() {
122         Resources res = getResources();
123         AnimatedImageDrawable aid1 = (AnimatedImageDrawable) res.getDrawable(R.drawable.animated);
124         AnimatedImageDrawable aid2 = (AnimatedImageDrawable) res.getDrawable(R.drawable.animated);
125 
126         final int originalAlpha = aid1.getAlpha();
127         assertEquals(255, originalAlpha);
128         assertEquals(255, aid2.getAlpha());
129 
130         try {
131             aid1.mutate();
132             aid1.setAlpha(100);
133             assertEquals(originalAlpha, aid2.getAlpha());
134         } finally {
135             res.getDrawable(R.drawable.animated).setAlpha(originalAlpha);
136         }
137     }
138 
createFromImageDecoder(int resId)139     private AnimatedImageDrawable createFromImageDecoder(int resId) {
140         Uri uri = Utils.getAsResourceUri(resId);
141         try {
142             ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
143             Drawable drawable = ImageDecoder.decodeDrawable(source);
144             assertTrue(drawable instanceof AnimatedImageDrawable);
145             return (AnimatedImageDrawable) drawable;
146         } catch (IOException e) {
147             fail("failed to create image from " + uri);
148             return null;
149         }
150     }
151 
decodeBitmap(int resId)152     private Bitmap decodeBitmap(int resId) {
153         Uri uri = Utils.getAsResourceUri(resId);
154         try {
155             ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
156             return ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
157                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
158             });
159         } catch (IOException e) {
160             fail("Failed to create Bitmap from " + uri);
161             return null;
162         }
163     }
164 
165     @Test
testDecodeAnimatedImageDrawable()166     public void testDecodeAnimatedImageDrawable() {
167         Drawable drawable = createFromImageDecoder(RES_ID);
168         assertEquals(WIDTH,  drawable.getIntrinsicWidth());
169         assertEquals(HEIGHT, drawable.getIntrinsicHeight());
170     }
171 
172     private static class Callback extends Animatable2Callback {
173         private final Drawable mDrawable;
174 
Callback(Drawable d)175         public Callback(Drawable d) {
176             mDrawable = d;
177         }
178 
179         @Override
onAnimationStart(Drawable drawable)180         public void onAnimationStart(Drawable drawable) {
181             assertNotNull(drawable);
182             assertEquals(mDrawable, drawable);
183             super.onAnimationStart(drawable);
184         }
185 
186         @Override
onAnimationEnd(Drawable drawable)187         public void onAnimationEnd(Drawable drawable) {
188             assertNotNull(drawable);
189             assertEquals(mDrawable, drawable);
190             super.onAnimationEnd(drawable);
191         }
192     };
193 
194     @Test(expected=IllegalStateException.class)
testRegisterWithoutLooper()195     public void testRegisterWithoutLooper() {
196         AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
197 
198         // registerAnimationCallback must be run on a thread with a Looper,
199         // which the test thread does not have.
200         Callback cb = new Callback(drawable);
201         drawable.registerAnimationCallback(cb);
202     }
203 
204     @Test
testRegisterCallback()205     public void testRegisterCallback() throws Throwable {
206         setupActivity();
207         AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
208 
209         mActivityRule.runOnUiThread(() -> {
210             // Register a callback.
211             Callback cb = new Callback(drawable);
212             drawable.registerAnimationCallback(cb);
213             assertTrue(drawable.unregisterAnimationCallback(cb));
214 
215             // Now that it has been removed, it cannot be removed again.
216             assertFalse(drawable.unregisterAnimationCallback(cb));
217         });
218     }
219 
220     @Test
testClearCallbacks()221     public void testClearCallbacks() throws Throwable {
222         setupActivity();
223         AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
224 
225         Callback[] callbacks = new Callback[] {
226             new Callback(drawable),
227             new Callback(drawable),
228             new Callback(drawable),
229             new Callback(drawable),
230             new Callback(drawable),
231             new Callback(drawable),
232             new Callback(drawable),
233             new Callback(drawable),
234         };
235 
236         mActivityRule.runOnUiThread(() -> {
237             for (Callback cb : callbacks) {
238                 drawable.registerAnimationCallback(cb);
239             }
240         });
241 
242         drawable.clearAnimationCallbacks();
243 
244         for (Callback cb : callbacks) {
245             // It has already been removed.
246             assertFalse(drawable.unregisterAnimationCallback(cb));
247         }
248     }
249 
250     @Test
testUnregisterCallback()251     public void testUnregisterCallback() throws Throwable {
252         setupActivity();
253         AnimatedImageDrawable drawable = createFromImageDecoder(R.drawable.animated);
254 
255         Callback cb = new Callback(drawable);
256         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
257             mImageView.setImageDrawable(drawable);
258 
259             drawable.registerAnimationCallback(cb);
260             assertTrue(drawable.unregisterAnimationCallback(cb));
261             drawable.setRepeatCount(0);
262             drawable.start();
263         });
264 
265         cb.waitForStart();
266         cb.assertStarted(false);
267 
268         cb.waitForEnd(DURATION * 2);
269         cb.assertEnded(false);
270     }
271 
272     @Test
273     @FlakyTest (bugId = 120280954)
testLifeCycle()274     public void testLifeCycle() throws Throwable {
275         setupActivity();
276         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
277 
278         // Only run the animation one time.
279         drawable.setRepeatCount(0);
280 
281         Callback cb = new Callback(drawable);
282         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
283             mImageView.setImageDrawable(drawable);
284 
285             drawable.registerAnimationCallback(cb);
286         });
287 
288         assertFalse(drawable.isRunning());
289         cb.assertStarted(false);
290         cb.assertEnded(false);
291 
292         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
293             drawable.start();
294             assertTrue(drawable.isRunning());
295         });
296         cb.waitForStart();
297         cb.assertStarted(true);
298 
299         // FIXME: Now that it seems the reason for the flakiness has been solved (b/129400990),
300         // reduce this extra duration workaround.
301         // Extra time, to wait for the message to post.
302         cb.waitForEnd(DURATION * 20);
303         cb.assertEnded(true);
304         assertFalse(drawable.isRunning());
305     }
306 
307     @Test
testLifeCycleSoftware()308     public void testLifeCycleSoftware() throws Throwable {
309         setupActivity();
310         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
311 
312         Bitmap bm = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
313                 Bitmap.Config.ARGB_8888);
314         Canvas canvas = new Canvas(bm);
315 
316         Callback cb = new Callback(drawable);
317         mActivityRule.runOnUiThread(() -> {
318             drawable.registerAnimationCallback(cb);
319             drawable.draw(canvas);
320         });
321 
322         assertFalse(drawable.isRunning());
323         cb.assertStarted(false);
324         cb.assertEnded(false);
325 
326         mActivityRule.runOnUiThread(() -> {
327             drawable.start();
328             assertTrue(drawable.isRunning());
329             drawable.draw(canvas);
330         });
331         cb.waitForStart();
332         cb.assertStarted(true);
333 
334         // Only run the animation one time.
335         drawable.setRepeatCount(0);
336 
337         // The drawable will prevent skipping frames, so we actually have to
338         // draw each frame. (Start with 1, since we already drew frame 0.)
339         for (int i = 1; i < NUM_FRAMES; i++) {
340             cb.waitForEnd(FRAME_DURATION);
341             cb.assertEnded(false);
342             mActivityRule.runOnUiThread(() -> {
343                 assertTrue(drawable.isRunning());
344                 drawable.draw(canvas);
345             });
346         }
347 
348         cb.waitForEnd(FRAME_DURATION);
349         assertFalse(drawable.isRunning());
350         cb.assertEnded(true);
351     }
352 
353     @Test
354     @FlakyTest (bugId = 72737527)
testAddCallbackAfterStart()355     public void testAddCallbackAfterStart() throws Throwable {
356         setupActivity();
357         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
358         Callback cb = new Callback(drawable);
359         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
360             mImageView.setImageDrawable(drawable);
361 
362             drawable.setRepeatCount(0);
363             drawable.start();
364             drawable.registerAnimationCallback(cb);
365         });
366 
367         // FIXME: Now that it seems the reason for the flakiness has been solved (b/129400990),
368         // reduce this extra duration workaround.
369         // Add extra duration to wait for the message posted by the end of the
370         // animation. This should help fix flakiness.
371         cb.waitForEnd(DURATION * 10);
372         cb.assertEnded(true);
373     }
374 
375     @Test
testStop()376     public void testStop() throws Throwable {
377         setupActivity();
378         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
379         Callback cb = new Callback(drawable);
380         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
381             mImageView.setImageDrawable(drawable);
382 
383             drawable.registerAnimationCallback(cb);
384 
385             drawable.start();
386             assertTrue(drawable.isRunning());
387         });
388 
389         cb.waitForStart();
390         cb.assertStarted(true);
391 
392         mActivityRule.runOnUiThread(() -> {
393             drawable.stop();
394             assertFalse(drawable.isRunning());
395         });
396 
397         // This duration may be overkill, but we need to wait for the message
398         // to post. Increasing it should help with flakiness on bots.
399         cb.waitForEnd(DURATION * 3);
400         cb.assertEnded(true);
401     }
402 
403     @Test
404     @FlakyTest (bugId = 72737527)
405     @Parameters({ "3", "5", "7", "16" })
testRepeatCounts(int repeatCount)406     public void testRepeatCounts(int repeatCount) throws Throwable {
407         setupActivity();
408         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
409         assertEquals(AnimatedImageDrawable.REPEAT_INFINITE, drawable.getRepeatCount());
410 
411         Callback cb = new Callback(drawable);
412         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
413             mImageView.setImageDrawable(drawable);
414 
415             drawable.registerAnimationCallback(cb);
416             drawable.setRepeatCount(repeatCount);
417             assertEquals(repeatCount, drawable.getRepeatCount());
418             drawable.start();
419         });
420 
421         cb.waitForStart();
422         cb.assertStarted(true);
423 
424         // The animation runs repeatCount + 1 total times.
425         cb.waitForEnd(DURATION * repeatCount);
426         cb.assertEnded(false);
427 
428         // FIXME: Now that it seems the reason for the flakiness has been solved (b/129400990),
429         // reduce this extra duration workaround.
430         cb.waitForEnd(DURATION * 30);
431         cb.assertEnded(true);
432 
433         drawable.setRepeatCount(AnimatedImageDrawable.REPEAT_INFINITE);
434         assertEquals(AnimatedImageDrawable.REPEAT_INFINITE, drawable.getRepeatCount());
435     }
436 
437     @Test
testRepeatCountInfinite()438     public void testRepeatCountInfinite() throws Throwable {
439         setupActivity();
440         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
441         Callback cb = new Callback(drawable);
442         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
443             mImageView.setImageDrawable(drawable);
444 
445             drawable.registerAnimationCallback(cb);
446             drawable.setRepeatCount(AnimatedImageDrawable.REPEAT_INFINITE);
447             drawable.start();
448         });
449 
450         // There is no way to truly test infinite, but let it run for a long
451         // time and verify that it's still running.
452         cb.waitForEnd(DURATION * 30);
453         cb.assertEnded(false);
454         assertTrue(drawable.isRunning());
455     }
456 
parametersForTestEncodedRepeats()457     public static Object[] parametersForTestEncodedRepeats() {
458         return new Object[] {
459             new Object[] { R.drawable.animated, AnimatedImageDrawable.REPEAT_INFINITE },
460             new Object[] { R.drawable.animated_one_loop, 1 },
461             new Object[] { R.drawable.webp_animated, AnimatedImageDrawable.REPEAT_INFINITE },
462             new Object[] { R.drawable.webp_animated_large, AnimatedImageDrawable.REPEAT_INFINITE },
463             new Object[] { R.drawable.webp_animated_icc_xmp, 31999 },
464             new Object[] { R.drawable.count_down_color_test, 0 },
465         };
466     }
467 
468     @Test
469     @Parameters(method = "parametersForTestEncodedRepeats")
testEncodedRepeats(int resId, int expectedRepeatCount)470     public void testEncodedRepeats(int resId, int expectedRepeatCount) {
471         AnimatedImageDrawable drawable = createFromImageDecoder(resId);
472         assertEquals(expectedRepeatCount, drawable.getRepeatCount());
473     }
474 
475     @Test
testGetOpacity()476     public void testGetOpacity() {
477         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
478         assertEquals(PixelFormat.TRANSLUCENT, drawable.getOpacity());
479     }
480 
481     @Test
testColorFilter()482     public void testColorFilter() {
483         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
484 
485         ColorFilter filter = new LightingColorFilter(0, Color.RED);
486         drawable.setColorFilter(filter);
487         assertEquals(filter, drawable.getColorFilter());
488 
489         Bitmap actual = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
490                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
491         {
492             Canvas canvas = new Canvas(actual);
493             drawable.draw(canvas);
494         }
495 
496         for (int i = 0; i < actual.getWidth(); ++i) {
497             for (int j = 0; j < actual.getHeight(); ++j) {
498                 int color = actual.getPixel(i, j);
499                 // The LightingColorFilter does not affect the transparent pixels,
500                 // so all pixels should either remain transparent or turn red.
501                 if (color != Color.RED && color != Color.TRANSPARENT) {
502                     fail("pixel at " + i + ", " + j + " does not match expected. "
503                             + "expected: " + Color.RED + " OR " + Color.TRANSPARENT
504                             + " actual: " + color);
505                 }
506             }
507         }
508     }
509 
510     @Test
testExif()511     public void testExif() {
512         // This animation has an exif orientation that makes it match R.drawable.animated (RES_ID).
513         AnimatedImageDrawable exifAnimation = createFromImageDecoder(R.drawable.animated_webp);
514 
515         Bitmap expected = decodeBitmap(RES_ID);
516         final int width = expected.getWidth();
517         final int height = expected.getHeight();
518 
519         assertEquals(width, exifAnimation.getIntrinsicWidth());
520         assertEquals(height, exifAnimation.getIntrinsicHeight());
521 
522         Bitmap actual = Bitmap.createBitmap(width, height, expected.getConfig(),
523                 expected.hasAlpha(), expected.getColorSpace());
524         {
525             Canvas canvas = new Canvas(actual);
526             exifAnimation.setBounds(0, 0, width, height);
527             exifAnimation.draw(canvas);
528         }
529 
530         // mseMargin was chosen by looking at the logs. The images are not exactly
531         // the same due to the fact that animated_webp's frames are encoded lossily,
532         // but the two images are perceptually identical.
533         final int mseMargin = 143;
534         final boolean lessThanMargin = true;
535         BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, lessThanMargin,
536                 expected.isPremultiplied());
537     }
538 
539     @Test
testPostProcess()540     public void testPostProcess() {
541         // Compare post processing a Rect in the middle of the (not-animating)
542         // image with drawing manually. They should be exactly the same.
543         BiFunction<Integer, Integer, Rect> rectCreator = (width, height) -> {
544             int quarterWidth  = width  / 4;
545             int quarterHeight = height / 4;
546             return new Rect(quarterWidth, quarterHeight,
547                     3 * quarterWidth, 3 * quarterHeight);
548         };
549 
550         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
551         Bitmap expected = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
552                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
553 
554         Paint paint = new Paint();
555         paint.setColor(Color.RED);
556 
557         {
558             Rect r = rectCreator.apply(drawable.getIntrinsicWidth(),
559                                        drawable.getIntrinsicHeight());
560             Canvas canvas = new Canvas(expected);
561             drawable.draw(canvas);
562 
563             for (int i = r.left; i < r.right; ++i) {
564                 for (int j = r.top; j < r.bottom; ++j) {
565                     assertNotEquals(Color.RED, expected.getPixel(i, j));
566                 }
567             }
568 
569             canvas.drawRect(r, paint);
570 
571             for (int i = r.left; i < r.right; ++i) {
572                 for (int j = r.top; j < r.bottom; ++j) {
573                     assertEquals(Color.RED, expected.getPixel(i, j));
574                 }
575             }
576         }
577 
578 
579         AnimatedImageDrawable testDrawable = null;
580         Uri uri = null;
581         try {
582             uri = Utils.getAsResourceUri(RES_ID);
583             ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
584             Drawable dr = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
585                 decoder.setPostProcessor((canvas) -> {
586                     canvas.drawRect(rectCreator.apply(canvas.getWidth(),
587                                                       canvas.getHeight()), paint);
588                     return PixelFormat.TRANSLUCENT;
589                 });
590             });
591             assertTrue(dr instanceof AnimatedImageDrawable);
592             testDrawable = (AnimatedImageDrawable) dr;
593         } catch (IOException e) {
594             fail("failed to create image from " + uri);
595         }
596 
597         Bitmap actual = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
598                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
599 
600         {
601             Canvas canvas = new Canvas(actual);
602             testDrawable.draw(canvas);
603         }
604 
605         assertTrue(BitmapUtils.compareBitmaps(expected, actual));
606     }
607 
608     @Test
testCreateFromXml()609     public void testCreateFromXml() throws XmlPullParserException, IOException {
610         Resources res = getResources();
611         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable_tag);
612         Drawable drawable = Drawable.createFromXml(res, parser);
613         assertNotNull(drawable);
614         assertTrue(drawable instanceof AnimatedImageDrawable);
615     }
616 
617     @Test
testCreateFromXmlClass()618     public void testCreateFromXmlClass() throws XmlPullParserException, IOException {
619         Resources res = getResources();
620         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable);
621         Drawable drawable = Drawable.createFromXml(res, parser);
622         assertNotNull(drawable);
623         assertTrue(drawable instanceof AnimatedImageDrawable);
624     }
625 
626     @Test
testCreateFromXmlClassAttribute()627     public void testCreateFromXmlClassAttribute() throws XmlPullParserException, IOException {
628         Resources res = getResources();
629         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable_class);
630         Drawable drawable = Drawable.createFromXml(res, parser);
631         assertNotNull(drawable);
632         assertTrue(drawable instanceof AnimatedImageDrawable);
633     }
634 
635     @Test(expected=XmlPullParserException.class)
testMissingSrcInflate()636     public void testMissingSrcInflate() throws XmlPullParserException, IOException  {
637         Resources res = getResources();
638         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable_nosrc);
639         Drawable drawable = Drawable.createFromXml(res, parser);
640     }
641 
642     @Test
testAutoMirrored()643     public void testAutoMirrored() {
644         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
645         assertFalse(drawable.isAutoMirrored());
646 
647         drawable.setAutoMirrored(true);
648         assertTrue(drawable.isAutoMirrored());
649 
650         drawable.setAutoMirrored(false);
651         assertFalse(drawable.isAutoMirrored());
652     }
653 
654     @Test
testAutoMirroredFromXml()655     public void testAutoMirroredFromXml() throws XmlPullParserException, IOException {
656         AnimatedImageDrawable drawable = parseXml(R.drawable.animatedimagedrawable_tag);
657         assertFalse(drawable.isAutoMirrored());
658 
659         drawable = parseXml(R.drawable.animatedimagedrawable_automirrored);
660         assertTrue(drawable.isAutoMirrored());
661     }
662 
parseXml(int resId)663     private AnimatedImageDrawable parseXml(int resId) throws XmlPullParserException, IOException {
664         Resources res = getResources();
665         XmlPullParser parser = res.getXml(resId);
666         Drawable drawable = Drawable.createFromXml(res, parser);
667         assertNotNull(drawable);
668         assertTrue(drawable instanceof AnimatedImageDrawable);
669         return (AnimatedImageDrawable) drawable;
670     }
671 
672     @Test
testAutoStartFromXml()673     public void testAutoStartFromXml() throws XmlPullParserException, IOException {
674         AnimatedImageDrawable drawable = parseXml(R.drawable.animatedimagedrawable_tag);
675         assertFalse(drawable.isRunning());
676 
677         drawable = parseXml(R.drawable.animatedimagedrawable_autostart_false);
678         assertFalse(drawable.isRunning());
679 
680         drawable = parseXml(R.drawable.animatedimagedrawable_autostart);
681         assertTrue(drawable.isRunning());
682     }
683 
drawAndCompare(Bitmap expected, Drawable drawable)684     private void drawAndCompare(Bitmap expected, Drawable drawable) {
685         Bitmap test = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
686                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
687         Canvas canvas = new Canvas(test);
688         drawable.draw(canvas);
689         assertTrue(BitmapUtils.compareBitmaps(expected, test));
690     }
691 
692     @Test
testAutoMirroredDrawing()693     public void testAutoMirroredDrawing() {
694         AnimatedImageDrawable drawable = createFromImageDecoder(RES_ID);
695         assertFalse(drawable.isAutoMirrored());
696 
697         final int width = drawable.getIntrinsicWidth();
698         final int height = drawable.getIntrinsicHeight();
699         Bitmap normal = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
700         {
701             Canvas canvas = new Canvas(normal);
702             drawable.draw(canvas);
703         }
704 
705         Bitmap flipped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
706         {
707             Canvas canvas = new Canvas(flipped);
708             canvas.translate(width, 0);
709             canvas.scale(-1, 1);
710             drawable.draw(canvas);
711         }
712 
713         for (int i = 0; i < width; ++i) {
714             for (int j = 0; j < height; ++j) {
715                 assertEquals(normal.getPixel(i, j), flipped.getPixel(width - 1 - i, j));
716             }
717         }
718 
719         drawable.setAutoMirrored(true);
720         drawAndCompare(normal, drawable);
721 
722         drawable.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
723         drawAndCompare(flipped, drawable);
724 
725         drawable.setAutoMirrored(false);
726         drawAndCompare(normal, drawable);
727     }
728 
729     @Test
testRepeatCountFromXml()730     public void testRepeatCountFromXml() throws XmlPullParserException, IOException {
731         Resources res = getResources();
732         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable_loop_count);
733         Drawable drawable = Drawable.createFromXml(res, parser);
734         assertNotNull(drawable);
735         assertTrue(drawable instanceof AnimatedImageDrawable);
736 
737         AnimatedImageDrawable aid = (AnimatedImageDrawable) drawable;
738         assertEquals(17, aid.getRepeatCount());
739     }
740 
741     @Test
testInfiniteRepeatCountFromXml()742     public void testInfiniteRepeatCountFromXml() throws XmlPullParserException, IOException {
743         // This image has an encoded repeat count of 1. Verify that.
744         Resources res = getResources();
745         Drawable drawable = res.getDrawable(R.drawable.animated_one_loop);
746         assertNotNull(drawable);
747         assertTrue(drawable instanceof AnimatedImageDrawable);
748         AnimatedImageDrawable aid = (AnimatedImageDrawable) drawable;
749         assertEquals(1, aid.getRepeatCount());
750 
751         // This layout uses the same image and overrides the repeat count to infinity.
752         XmlPullParser parser = res.getXml(R.drawable.animatedimagedrawable_loop_count_infinite);
753         drawable = Drawable.createFromXml(res, parser);
754         assertNotNull(drawable);
755         assertTrue(drawable instanceof AnimatedImageDrawable);
756 
757         aid = (AnimatedImageDrawable) drawable;
758         assertEquals(AnimatedImageDrawable.REPEAT_INFINITE, aid.getRepeatCount());
759     }
760 
761     // Verify that decoding on the AnimatedImageThread works.
decodeInBackground(AnimatedImageDrawable drawable)762     private void decodeInBackground(AnimatedImageDrawable drawable) throws Throwable {
763         final Callback cb = new Callback(drawable);
764         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mImageView, () -> {
765             mImageView.setImageDrawable(drawable);
766 
767             drawable.registerAnimationCallback(cb);
768             drawable.start();
769         });
770 
771         // The first frame was decoded in the thread that created the
772         // AnimatedImageDrawable. Wait long enough to decode further threads on
773         // the AnimatedImageThread, which was not created with a JNI interface
774         // pointer.
775         cb.waitForStart();
776         cb.waitForEnd(DURATION * 2);
777     }
778 
779     @Test
testInputStream()780     public void testInputStream() throws Throwable {
781         setupActivity();
782         Resources res = getResources();
783         try (InputStream in = res.openRawResource(R.drawable.animated)) {
784             ImageDecoder.Source src =
785                     ImageDecoder.createSource(res, in, Bitmap.DENSITY_NONE);
786             AnimatedImageDrawable drawable =
787                     (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
788             decodeInBackground(drawable);
789         }
790 
791     }
792 
getAsByteArray()793     private byte[] getAsByteArray() {
794         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
795         try (InputStream in = getResources().openRawResource(RES_ID)) {
796             byte[] buf = new byte[4096];
797             int bytesRead;
798             while ((bytesRead = in.read(buf)) != -1) {
799                 outputStream.write(buf, 0, bytesRead);
800             }
801         } catch (IOException e) {
802             fail("Failed to read resource: " + e);
803         }
804 
805         return outputStream.toByteArray();
806     }
807 
getAsDirectByteBuffer()808     private ByteBuffer getAsDirectByteBuffer() {
809         byte[] array = getAsByteArray();
810         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
811         byteBuffer.put(array);
812         byteBuffer.position(0);
813         return byteBuffer;
814     }
815 
createFromByteBuffer(ByteBuffer byteBuffer)816     private AnimatedImageDrawable createFromByteBuffer(ByteBuffer byteBuffer) {
817         ImageDecoder.Source src = ImageDecoder.createSource(byteBuffer);
818         try {
819             return (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
820         } catch (IOException e) {
821             fail("Failed to create decoder: " + e);
822             return null;
823         }
824     }
825 
826     @Test
testByteBuffer()827     public void testByteBuffer() throws Throwable {
828         setupActivity();
829         // Natively, this tests ByteArrayStream.
830         byte[] array = getAsByteArray();
831         ByteBuffer byteBuffer = ByteBuffer.wrap(array);
832         final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
833         decodeInBackground(drawable);
834     }
835 
836     @Test
testReadOnlyByteBuffer()837     public void testReadOnlyByteBuffer() throws Throwable {
838         setupActivity();
839         // Natively, this tests ByteBufferStream.
840         byte[] array = getAsByteArray();
841         ByteBuffer byteBuffer = ByteBuffer.wrap(array).asReadOnlyBuffer();
842         final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
843         decodeInBackground(drawable);
844     }
845 
846     @Test
testDirectByteBuffer()847     public void testDirectByteBuffer() throws Throwable {
848         setupActivity();
849         ByteBuffer byteBuffer = getAsDirectByteBuffer();
850         final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
851         decodeInBackground(drawable);
852     }
853 
854     @Test
855     @RequiresFlagsEnabled(Flags.FLAG_ANIMATED_IMAGE_DRAWABLE_FILTER_BITMAP)
testSetFilterBitmap()856     public void testSetFilterBitmap() {
857         Resources res = getResources();
858         AnimatedImageDrawable animatedImageDrawable =
859                 (AnimatedImageDrawable) res.getDrawable(R.drawable.animated);
860 
861         assertTrue(animatedImageDrawable.isFilterBitmap());
862 
863         animatedImageDrawable.setFilterBitmap(false);
864         assertFalse(animatedImageDrawable.isFilterBitmap());
865 
866         animatedImageDrawable.setFilterBitmap(true);
867         assertTrue(animatedImageDrawable.isFilterBitmap());
868     }
869 }
870