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