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