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