/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.animated.factory; import android.graphics.Bitmap; import android.graphics.Rect; import com.facebook.animated.webp.WebPImage; import com.facebook.common.memory.PooledByteBuffer; import com.facebook.common.references.CloseableReference; import com.facebook.common.references.ResourceReleaser; import com.facebook.common.soloader.SoLoaderShim; import com.facebook.imageformat.ImageFormat; import com.facebook.imagepipeline.animated.base.AnimatedDrawableBackend; import com.facebook.imagepipeline.animated.base.AnimatedImageResult; import com.facebook.imagepipeline.animated.factory.AnimatedImageFactoryImpl; import com.facebook.imagepipeline.animated.impl.AnimatedDrawableBackendProvider; import com.facebook.imagepipeline.animated.impl.AnimatedImageCompositor; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; import com.facebook.imagepipeline.common.ImageDecodeOptions; import com.facebook.imagepipeline.image.CloseableAnimatedImage; import com.facebook.imagepipeline.image.EncodedImage; import com.facebook.imagepipeline.testing.MockBitmapFactory; import com.facebook.imagepipeline.testing.TrivialPooledByteBuffer; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Tests for {@link AnimatedImageFactory} */ @RunWith(RobolectricTestRunner.class) @PrepareOnlyThisForTest({ WebPImage.class, AnimatedImageFactoryImpl.class, AnimatedImageCompositor.class}) @PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) public class AnimatedImageFactoryWebPImplTest { private static final Bitmap.Config DEFAULT_BITMAP_CONFIG = Bitmap.Config.ARGB_8888; @Rule public PowerMockRule rule = new PowerMockRule(); static { SoLoaderShim.setInTestMode(); } private static ResourceReleaser<PooledByteBuffer> FAKE_RESOURCE_RELEASER = new ResourceReleaser<PooledByteBuffer>() { @Override public void release(PooledByteBuffer value) { } }; private static ResourceReleaser<Bitmap> FAKE_BITMAP_RESOURCE_RELEASER = new ResourceReleaser<Bitmap>() { @Override public void release(Bitmap value) { } }; private AnimatedDrawableBackendProvider mMockAnimatedDrawableBackendProvider; private PlatformBitmapFactory mMockBitmapFactory; private AnimatedImageFactory mAnimatedImageFactory; private WebPImage mWebPImageMock; @Before public void setup() { PowerMockito.mockStatic(WebPImage.class); mWebPImageMock = mock(WebPImage.class); mMockAnimatedDrawableBackendProvider = mock(AnimatedDrawableBackendProvider.class); mMockBitmapFactory = mock(PlatformBitmapFactory.class); mAnimatedImageFactory = new AnimatedImageFactoryImpl( mMockAnimatedDrawableBackendProvider, mMockBitmapFactory); ((AnimatedImageFactoryImpl) mAnimatedImageFactory).sWebpAnimatedImageDecoder = mWebPImageMock; } @Test public void testCreateDefaults() { WebPImage mockWebPImage = mock(WebPImage.class); // Expect a call to WebPImage.create TrivialPooledByteBuffer byteBuffer = createByteBuffer(); when(mWebPImageMock.decode(byteBuffer.getNativePtr(), byteBuffer.size())) .thenReturn(mockWebPImage); EncodedImage encodedImage = new EncodedImage( CloseableReference.of(byteBuffer, FAKE_RESOURCE_RELEASER)); encodedImage.setImageFormat(ImageFormat.UNKNOWN); CloseableAnimatedImage closeableImage = (CloseableAnimatedImage) mAnimatedImageFactory.decodeWebP( encodedImage, ImageDecodeOptions.defaults(), DEFAULT_BITMAP_CONFIG); // Verify we got the right result AnimatedImageResult imageResult = closeableImage.getImageResult(); assertSame(mockWebPImage, imageResult.getImage()); assertNull(imageResult.getPreviewBitmap()); assertFalse(imageResult.hasDecodedFrame(0)); // Should not have interacted with these. verifyZeroInteractions(mMockAnimatedDrawableBackendProvider); verifyZeroInteractions(mMockBitmapFactory); } @Test public void testCreateWithPreviewBitmap() throws Exception { WebPImage mockWebPImage = mock(WebPImage.class); Bitmap mockBitmap = MockBitmapFactory.create(50, 50, DEFAULT_BITMAP_CONFIG); // Expect a call to WebPImage.create TrivialPooledByteBuffer byteBuffer = createByteBuffer(); when(mWebPImageMock.decode(byteBuffer.getNativePtr(), byteBuffer.size())) .thenReturn(mockWebPImage); when(mockWebPImage.getWidth()).thenReturn(50); when(mockWebPImage.getHeight()).thenReturn(50); // For decoding preview frame, expect some calls. final AnimatedDrawableBackend mockAnimatedDrawableBackend = createAnimatedDrawableBackendMock(1); when(mMockAnimatedDrawableBackendProvider.get( any(AnimatedImageResult.class), isNull(Rect.class))) .thenReturn(mockAnimatedDrawableBackend); when(mMockBitmapFactory.createBitmapInternal(50, 50, DEFAULT_BITMAP_CONFIG)) .thenReturn(CloseableReference.of(mockBitmap, FAKE_BITMAP_RESOURCE_RELEASER)); AnimatedImageCompositor mockCompositor = mock(AnimatedImageCompositor.class); PowerMockito.whenNew(AnimatedImageCompositor.class) .withAnyArguments() .thenReturn(mockCompositor); ImageDecodeOptions imageDecodeOptions = ImageDecodeOptions.newBuilder() .setDecodePreviewFrame(true) .build(); EncodedImage encodedImage = new EncodedImage( CloseableReference.of(byteBuffer, FAKE_RESOURCE_RELEASER)); encodedImage.setImageFormat(ImageFormat.UNKNOWN); CloseableAnimatedImage closeableImage = (CloseableAnimatedImage) mAnimatedImageFactory.decodeWebP( encodedImage, imageDecodeOptions, DEFAULT_BITMAP_CONFIG); // Verify we got the right result AnimatedImageResult imageResult = closeableImage.getImageResult(); assertSame(mockWebPImage, imageResult.getImage()); assertNotNull(imageResult.getPreviewBitmap()); assertFalse(imageResult.hasDecodedFrame(0)); // Should not have interacted with these. verify(mMockAnimatedDrawableBackendProvider).get( any(AnimatedImageResult.class), isNull(Rect.class)); verifyNoMoreInteractions(mMockAnimatedDrawableBackendProvider); verify(mMockBitmapFactory).createBitmapInternal(50, 50, DEFAULT_BITMAP_CONFIG); verifyNoMoreInteractions(mMockBitmapFactory); verify(mockCompositor).renderFrame(0, mockBitmap); } @Test public void testCreateWithDecodeAlFrames() throws Exception { WebPImage mockWebPImage = mock(WebPImage.class); Bitmap mockBitmap1 = MockBitmapFactory.create(50, 50, DEFAULT_BITMAP_CONFIG); Bitmap mockBitmap2 = MockBitmapFactory.create(50, 50, DEFAULT_BITMAP_CONFIG); // Expect a call to WebPImage.create TrivialPooledByteBuffer byteBuffer = createByteBuffer(); when(mWebPImageMock.decode(byteBuffer.getNativePtr(), byteBuffer.size())) .thenReturn(mockWebPImage); when(mockWebPImage.getWidth()).thenReturn(50); when(mockWebPImage.getHeight()).thenReturn(50); // For decoding preview frame, expect some calls. final AnimatedDrawableBackend mockAnimatedDrawableBackend = createAnimatedDrawableBackendMock(2); when( mMockAnimatedDrawableBackendProvider.get( any(AnimatedImageResult.class), isNull(Rect.class))) .thenReturn(mockAnimatedDrawableBackend); when(mMockBitmapFactory.createBitmapInternal(50, 50, DEFAULT_BITMAP_CONFIG)) .thenReturn(CloseableReference.of(mockBitmap1, FAKE_BITMAP_RESOURCE_RELEASER)) .thenReturn(CloseableReference.of(mockBitmap2, FAKE_BITMAP_RESOURCE_RELEASER)); AnimatedImageCompositor mockCompositor = mock(AnimatedImageCompositor.class); PowerMockito.whenNew(AnimatedImageCompositor.class) .withAnyArguments() .thenReturn(mockCompositor); ImageDecodeOptions imageDecodeOptions = ImageDecodeOptions.newBuilder() .setDecodePreviewFrame(true) .setDecodeAllFrames(true) .build(); EncodedImage encodedImage = new EncodedImage( CloseableReference.of(byteBuffer, FAKE_RESOURCE_RELEASER)); encodedImage.setImageFormat(ImageFormat.UNKNOWN); CloseableAnimatedImage closeableImage = (CloseableAnimatedImage) mAnimatedImageFactory.decodeWebP( encodedImage, imageDecodeOptions, DEFAULT_BITMAP_CONFIG); // Verify we got the right result AnimatedImageResult imageResult = closeableImage.getImageResult(); assertSame(mockWebPImage, imageResult.getImage()); assertNotNull(imageResult.getDecodedFrame(0)); assertNotNull(imageResult.getDecodedFrame(1)); assertNotNull(imageResult.getPreviewBitmap()); // Should not have interacted with these. verify(mMockAnimatedDrawableBackendProvider).get( any(AnimatedImageResult.class), isNull(Rect.class)); verifyNoMoreInteractions(mMockAnimatedDrawableBackendProvider); verify(mMockBitmapFactory, times(2)).createBitmapInternal(50, 50, DEFAULT_BITMAP_CONFIG); verifyNoMoreInteractions(mMockBitmapFactory); verify(mockCompositor).renderFrame(0, mockBitmap1); verify(mockCompositor).renderFrame(1, mockBitmap2); } private TrivialPooledByteBuffer createByteBuffer() { byte[] buf = new byte[16]; return new TrivialPooledByteBuffer(buf); } /** * Creates the mock for the AnimatedDrawableBackend with the number of frame * @param frameCount The number of frame to mock */ private AnimatedDrawableBackend createAnimatedDrawableBackendMock(final int frameCount) { // For decoding preview frame, expect some calls. final AnimatedDrawableBackend mockAnimatedDrawableBackend = mock(AnimatedDrawableBackend.class); when(mockAnimatedDrawableBackend.getFrameCount()).thenReturn(frameCount); when(mockAnimatedDrawableBackend.getWidth()).thenReturn(50); when(mockAnimatedDrawableBackend.getHeight()).thenReturn(50); return mockAnimatedDrawableBackend; } }