package com.bumptech.glide; import static com.bumptech.glide.request.RequestOptions.decodeTypeOf; import static com.bumptech.glide.request.RequestOptions.placeholderOf; import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Matchers.notNull; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.view.ViewGroup; import android.widget.ImageView; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.Key; import com.bumptech.glide.load.Options; import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.engine.cache.DiskCache; import com.bumptech.glide.load.engine.cache.MemoryCache; import com.bumptech.glide.load.engine.executor.GlideExecutor; import com.bumptech.glide.load.engine.executor.MockGlideExecutor; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.MultiModelLoaderFactory; import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.manager.Lifecycle; import com.bumptech.glide.manager.RequestManagerTreeNode; import com.bumptech.glide.module.GlideModule; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SizeReadyCallback; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.tests.GlideShadowLooper; import com.bumptech.glide.tests.Util; import com.bumptech.glide.testutil.TestResourceUtil; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.res.builder.RobolectricPackageManager; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowBitmap; /** * Tests for the {@link Glide} interface and singleton. */ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 18, shadows = { GlideTest.ShadowFileDescriptorContentResolver.class, GlideTest.ShadowMediaMetadataRetriever.class, GlideShadowLooper.class, GlideTest.MutableShadowBitmap.class }) @SuppressWarnings({"unchecked", "deprecation"}) public class GlideTest { @SuppressWarnings("rawtypes") private Target target = null; private ImageView imageView; private RequestManager requestManager; @Before public void setUp() throws Exception { Glide.tearDown(); RobolectricPackageManager pm = RuntimeEnvironment.getRobolectricPackageManager(); ApplicationInfo info = pm.getApplicationInfo(RuntimeEnvironment.application.getPackageName(), 0); info.metaData = new Bundle(); info.metaData.putString(SetupModule.class.getName(), "GlideModule"); // Ensure that target's size ready callback will be called synchronously. target = mock(Target.class); imageView = new ImageView(RuntimeEnvironment.application); imageView.setLayoutParams(new ViewGroup.LayoutParams(100, 100)); imageView.layout(0, 0, 100, 100); doAnswer(new CallSizeReady()).when(target).getSize(isA(SizeReadyCallback.class)); Handler bgHandler = mock(Handler.class); when(bgHandler.post(isA(Runnable.class))).thenAnswer(new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { Runnable runnable = (Runnable) invocation.getArguments()[0]; runnable.run(); return true; } }); Lifecycle lifecycle = mock(Lifecycle.class); RequestManagerTreeNode treeNode = mock(RequestManagerTreeNode.class); requestManager = new RequestManager(Glide.get(getContext()), lifecycle, treeNode); requestManager.resumeRequests(); } @After public void tearDown() { Glide.tearDown(); } @Test public void testCanSetMemoryCategory() { MemoryCache memoryCache = mock(MemoryCache.class); BitmapPool bitmapPool = mock(BitmapPool.class); MemoryCategory memoryCategory = MemoryCategory.NORMAL; Glide glide = new GlideBuilder().setMemoryCache(memoryCache).setBitmapPool(bitmapPool) .build(getContext()); glide.setMemoryCategory(memoryCategory); verify(memoryCache).setSizeMultiplier(eq(memoryCategory.getMultiplier())); verify(bitmapPool).setSizeMultiplier(eq(memoryCategory.getMultiplier())); } @Test public void testClearMemory() { BitmapPool bitmapPool = mock(BitmapPool.class); MemoryCache memoryCache = mock(MemoryCache.class); Glide glide = new GlideBuilder().setBitmapPool(bitmapPool).setMemoryCache(memoryCache) .build(getContext()); glide.clearMemory(); verify(bitmapPool).clearMemory(); verify(memoryCache).clearMemory(); } @Test public void testTrimMemory() { BitmapPool bitmapPool = mock(BitmapPool.class); MemoryCache memoryCache = mock(MemoryCache.class); Glide glide = new GlideBuilder().setBitmapPool(bitmapPool).setMemoryCache(memoryCache) .build(getContext()); final int level = 123; glide.trimMemory(level); verify(bitmapPool).trimMemory(eq(level)); verify(memoryCache).trimMemory(eq(level)); } @Test public void testFileDefaultLoaderWithInputStream() throws Exception { registerFailFactory(File.class, ParcelFileDescriptor.class); runTestFileDefaultLoader(); } @Test public void testFileDefaultLoaderWithFileDescriptor() throws Exception { registerFailFactory(File.class, InputStream.class); runTestFileDefaultLoader(); } @Test public void testFileDefaultLoader() { runTestFileDefaultLoader(); } private void runTestFileDefaultLoader() { File file = new File("fake"); mockUri(Uri.fromFile(file)); requestManager.load(file).into(target); requestManager.load(file).into(imageView); verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test public void testUrlDefaultLoader() throws MalformedURLException { URL url = new URL("http://www.google.com"); requestManager.load(url).into(target); requestManager.load(url).into(imageView); verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test public void testAsBitmapOption() { Uri uri = Uri.parse("content://something/else"); mockUri(uri); requestManager.asBitmap().load(uri).into(target); verify(target).onResourceReady(isA(Bitmap.class), isA(Transition.class)); } @Test public void testToBytesOption() { Uri uri = Uri.parse("content://something/else"); mockUri(uri); requestManager.as(byte[].class).apply(decodeTypeOf(Bitmap.class)).load(uri).into(target); verify(target).onResourceReady(isA(byte[].class), isA(Transition.class)); } @Test public void testUriDefaultLoaderWithInputStream() throws Exception { registerFailFactory(Uri.class, ParcelFileDescriptor.class); runTestUriDefaultLoader(); } @Test public void testUriDefaultLoaderWithFileDescriptor() throws Exception { registerFailFactory(Uri.class, InputStream.class); runTestUriDefaultLoader(); } @Test public void testUriDefaultLoader() { runTestUriDefaultLoader(); } private void runTestUriDefaultLoader() { Uri uri = Uri.parse("content://test/something"); mockUri(uri); requestManager.load(uri).into(target); requestManager.load(uri).into(imageView); verify(target).onResourceReady(notNull(), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test public void testStringDefaultLoaderWithUrl() { runTestStringDefaultLoader("http://www.google.com"); } @Test public void testFileStringDefaultLoaderWithInputStream() throws Exception { registerFailFactory(String.class, ParcelFileDescriptor.class); runTestFileStringDefaultLoader(); } @Test public void testFileStringDefaultLoaderWithFileDescriptor() throws Exception { registerFailFactory(String.class, ParcelFileDescriptor.class); runTestFileStringDefaultLoader(); } @Test public void testFileStringDefaultLoader() { runTestFileStringDefaultLoader(); } private void runTestFileStringDefaultLoader() { String path = "/some/random/path"; mockUri(Uri.fromFile(new File(path))); runTestStringDefaultLoader(path); } @Test public void testUriStringDefaultLoaderWithInputStream() throws Exception { registerFailFactory(String.class, ParcelFileDescriptor.class); runTestUriStringDefaultLoader(); } @Test public void testUriStringDefaultLoaderWithFileDescriptor() throws Exception { registerFailFactory(String.class, InputStream.class); runTestUriStringDefaultLoader(); } @Test public void testUriStringDefaultLoader() { runTestUriStringDefaultLoader(); } private void runTestUriStringDefaultLoader() { String stringUri = "content://some/random/uri"; mockUri(Uri.parse(stringUri)); runTestStringDefaultLoader(stringUri); } private void runTestStringDefaultLoader(String string) { requestManager.load(string).listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { throw new RuntimeException("Load failed"); } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { return false; } }).into(target); requestManager.load(string).into(imageView); verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test public void testIntegerDefaultLoaderWithInputStream() throws Exception { registerFailFactory(Integer.class, ParcelFileDescriptor.class); runTestIntegerDefaultLoader(); } @Test public void testIntegerDefaultLoaderWithFileDescriptor() throws Exception { registerFailFactory(Integer.class, InputStream.class); runTestIntegerDefaultLoader(); } @Test public void testIntegerDefaultLoader() { runTestIntegerDefaultLoader(); } private void runTestIntegerDefaultLoader() { int integer = android.R.drawable.star_on; mockUri("android.resource://" + "android" + "/drawable/star_on"); requestManager.load(integer).into(target); requestManager.load(integer).into(imageView); verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test public void testByteArrayDefaultLoader() { byte[] bytes = new byte[10]; requestManager.load(bytes).into(target); requestManager.load(bytes).into(imageView); verify(target).onResourceReady(isA(BitmapDrawable.class), isA(Transition.class)); verify(target).setRequest((Request) notNull()); assertNotNull(imageView.getDrawable()); } @Test(expected = Exception.class) public void testUnregisteredModelThrowsException() { Float unregistered = 0.5f; requestManager.load(unregistered).into(target); } @Test @SuppressWarnings("unchecked") public void testNonDefaultModelWithRegisteredFactoryDoesNotThrow() { registerMockStreamModelLoader(Float.class); requestManager.load(0.5f).into(target); } @Test public void testReceivesGif() throws IOException { String fakeUri = "content://fake"; InputStream testGifData = openResource("test.gif"); mockUri(Uri.parse(fakeUri), testGifData); requestManager.asGif().load(fakeUri).into(target); verify(target).onResourceReady(isA(GifDrawable.class), isA(Transition.class)); } @Test public void testReceivesGifBytes() throws IOException { String fakeUri = "content://fake"; InputStream testGifData = openResource("test.gif"); mockUri(Uri.parse(fakeUri), testGifData); requestManager.as(byte[].class).apply(decodeTypeOf(GifDrawable.class)).load(fakeUri) .into(target); verify(target).onResourceReady(isA(byte[].class), isA(Transition.class)); } @Test public void testReceivesBitmapBytes() { String fakeUri = "content://fake"; mockUri(fakeUri); requestManager.as(byte[].class).apply(decodeTypeOf(Bitmap.class)).load(fakeUri).into(target); verify(target).onResourceReady(isA(byte[].class), isA(Transition.class)); } @Test public void testReceivesThumbnails() { String full = mockUri("content://full"); String thumb = mockUri("content://thumb"); requestManager .load(full) .thumbnail(requestManager.load(thumb)) .into(target); verify(target, times(2)).onResourceReady(isA(Drawable.class), isA(Transition.class)); } @Test public void testReceivesRecursiveThumbnails() { requestManager.load(mockUri("content://first")).thumbnail( requestManager.load(mockUri("content://second")).thumbnail( requestManager.load(mockUri("content://third")).thumbnail( requestManager.load(mockUri("content://fourth"))))) .into(target); verify(target, times(4)).onResourceReady(isA(Drawable.class), isA(Transition.class)); } @Test public void testReceivesRecursiveThumbnailWithPercentage() { requestManager.load(mockUri("content://first")) .thumbnail(requestManager.load(mockUri("content://second")).thumbnail(0.5f)) .into(target); verify(target, times(3)).onResourceReady(isA(Drawable.class), isA(Transition.class)); } @Test public void testNullModelInGenericImageLoadDoesNotThrow() { requestManager.load(null).into(target); } @Test public void testNullModelInGenericVideoLoadDoesNotThrow() { requestManager.load(null).into(target); } @Test public void testNullModelInGenericLoadDoesNotThrow() { requestManager.load(null).into(target); } @Test public void testNullModelDoesNotThrow() { Drawable drawable = new ColorDrawable(Color.RED); requestManager .load(null) .apply(placeholderOf(drawable)) .into(target); verify(target).onLoadFailed(eq(drawable)); } @Test public void testNullModelPrefersErrorDrawable() { Drawable placeholder = new ColorDrawable(Color.GREEN); Drawable error = new ColorDrawable(Color.RED); requestManager .load(null) .apply(placeholderOf(placeholder) .error(error)) .into(target); verify(target).onLoadFailed(eq(error)); } @Test public void testNullModelPrefersFallbackDrawable() { Drawable placeholder = new ColorDrawable(Color.GREEN); Drawable error = new ColorDrawable(Color.RED); Drawable fallback = new ColorDrawable(Color.BLUE); requestManager .load(null) .apply(placeholderOf(placeholder) .error(error) .fallback(fallback)) .into(target); verify(target).onLoadFailed(eq(fallback)); } @Test public void testByteData() { byte[] data = new byte[] { 1, 2, 3, 4, 5, 6 }; requestManager.load(data).into(target); } @Test public void testClone() throws IOException { Target<Drawable> firstTarget = mock(Target.class); doAnswer(new CallSizeReady(100, 100)).when(firstTarget).getSize(isA(SizeReadyCallback.class)); Target<Drawable> secondTarget = mock(Target.class); doAnswer(new CallSizeReady(100, 100)).when(secondTarget).getSize(isA(SizeReadyCallback.class)); RequestBuilder<Drawable> firstRequest = requestManager .load(mockUri("content://first")); firstRequest.into(firstTarget); firstRequest.clone() .apply(placeholderOf(new ColorDrawable(Color.RED))) .into(secondTarget); verify(firstTarget).onResourceReady(isA(Drawable.class), isA(Transition.class)); verify(secondTarget).onResourceReady(notNull(Drawable.class), isA(Transition.class)); } @SuppressWarnings("unchecked") private <T, Z> void registerFailFactory(Class<T> failModel, Class<Z> failResource) throws Exception { DataFetcher<Z> failFetcher = mock(DataFetcher.class); doAnswer(new Util.CallDataReady<>(null)) .when(failFetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); when(failFetcher.getDataClass()).thenReturn(failResource); ModelLoader<T, Z> failLoader = mock(ModelLoader.class); when(failLoader.buildLoadData(isA(failModel), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), failFetcher)); when(failLoader.handles(isA(failModel))).thenReturn(true); ModelLoaderFactory<T, Z> failFactory = mock(ModelLoaderFactory.class); when(failFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(failLoader); Glide.get(getContext()).getRegistry().prepend(failModel, failResource, failFactory); } private String mockUri(String uriString) { return mockUri(Uri.parse(uriString), null); } private String mockUri(Uri uri) { return mockUri(uri, null); } private String mockUri(Uri uri, InputStream is) { if (is == null) { is = new ByteArrayInputStream(new byte[0]); } ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver(); ShadowFileDescriptorContentResolver shadowContentResolver = (ShadowFileDescriptorContentResolver) Shadow.extract(contentResolver); shadowContentResolver.registerInputStream(uri, is); AssetFileDescriptor assetFileDescriptor = mock(AssetFileDescriptor.class); ParcelFileDescriptor parcelFileDescriptor = mock(ParcelFileDescriptor.class); when(assetFileDescriptor.getParcelFileDescriptor()).thenReturn(parcelFileDescriptor); shadowContentResolver.registerAssetFileDescriptor(uri, assetFileDescriptor); return uri.toString(); } private Context getContext() { return RuntimeEnvironment.application; } @SuppressWarnings("unchecked") private <T> void registerMockStreamModelLoader(final Class<T> modelClass) { ModelLoader<T, InputStream> modelLoader = mockStreamModelLoader(modelClass); ModelLoaderFactory<T, InputStream> modelLoaderFactory = mock(ModelLoaderFactory.class); when(modelLoaderFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(modelLoader); Glide.get(RuntimeEnvironment.application).getRegistry() .prepend(modelClass, InputStream.class, modelLoaderFactory); } @SuppressWarnings("unchecked") private <T> ModelLoader<T, InputStream> mockStreamModelLoader(final Class<T> modelClass) { ModelLoader<T, InputStream> modelLoader = mock(ModelLoader.class); DataFetcher<InputStream> fetcher = mock(DataFetcher.class); try { doAnswer(new Util.CallDataReady<>(new ByteArrayInputStream(new byte[0]))).when(fetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); } catch (Exception e) { // Do nothing. } when(fetcher.getDataClass()).thenReturn(InputStream.class); when(modelLoader.buildLoadData(isA(modelClass), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), fetcher)); when(modelLoader.handles(isA(modelClass))).thenReturn(true); return modelLoader; } private InputStream openResource(String imageName) throws IOException { return TestResourceUtil.openResource(getClass(), imageName); } private static class CallSizeReady implements Answer<Void> { private int width; private int height; public CallSizeReady() { this(100, 100); } public CallSizeReady(int width, int height) { this.width = width; this.height = height; } @Override public Void answer(InvocationOnMock invocation) throws Throwable { SizeReadyCallback cb = (SizeReadyCallback) invocation.getArguments()[0]; cb.onSizeReady(width, height); return null; } } public static class SetupModule implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { // Run all tasks on the main thread so they complete synchronously. GlideExecutor executor = MockGlideExecutor.newMainThreadExecutor(); DiskCache.Factory diskCacheFactory = mock(DiskCache.Factory.class); when(diskCacheFactory.build()).thenReturn(mock(DiskCache.class)); builder.setMemoryCache(mock(MemoryCache.class)).setDiskCache(diskCacheFactory) .setResizeExecutor(executor).setDiskCacheExecutor(executor); } @Override public void registerComponents(Context context, Registry registry) { registerMockModelLoader(GlideUrl.class, InputStream.class, new ByteArrayInputStream(new byte[0]), registry); registerMockModelLoader(File.class, InputStream.class, new ByteArrayInputStream(new byte[0]), registry); registerMockModelLoader(File.class, ParcelFileDescriptor.class, mock(ParcelFileDescriptor.class), registry); registerMockModelLoader(File.class, ByteBuffer.class, ByteBuffer.allocate(10), registry); } private static <X, Y> void registerMockModelLoader(Class<X> modelClass, Class<Y> dataClass, Y loadedData, Registry registry) { DataFetcher<Y> mockStreamFetcher = mock(DataFetcher.class); when(mockStreamFetcher.getDataClass()).thenReturn(dataClass); try { doAnswer(new Util.CallDataReady<>(loadedData)) .when(mockStreamFetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); } catch (Exception e) { throw new RuntimeException(e); } ModelLoader<X, Y> mockUrlLoader = mock(ModelLoader.class); when(mockUrlLoader.buildLoadData(isA(modelClass), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), mockStreamFetcher)); when(mockUrlLoader.handles(isA(modelClass))).thenReturn(true); ModelLoaderFactory<X, Y> mockUrlLoaderFactory = mock(ModelLoaderFactory.class); when(mockUrlLoaderFactory.build(isA(MultiModelLoaderFactory.class))) .thenReturn(mockUrlLoader); registry.replace(modelClass, dataClass, mockUrlLoaderFactory); } } // TODO: Extending ShadowContentResolver results in exceptions because of some state issues // where we seem to get one content resolver shadow in one part of the test and a different one in // a different part of the test. Each one ends up with different registered uris, which causes // tests to fail. We shouldn't need to do this, but using static maps seems to fix the issue. @Implements(value = ContentResolver.class) @SuppressWarnings("unused") public static class ShadowFileDescriptorContentResolver { private static final Map<Uri, AssetFileDescriptor> URI_TO_FILE_DESCRIPTOR = new HashMap<>(); private static final Map<Uri, InputStream> URI_TO_INPUT_STREAMS = new HashMap<>(); @Resetter public static void reset() { URI_TO_INPUT_STREAMS.clear(); URI_TO_FILE_DESCRIPTOR.clear(); } public void registerInputStream(Uri uri, InputStream inputStream) { URI_TO_INPUT_STREAMS.put(uri, inputStream); } public void registerAssetFileDescriptor(Uri uri, AssetFileDescriptor assetFileDescriptor) { URI_TO_FILE_DESCRIPTOR.put(uri, assetFileDescriptor); } @Implementation public InputStream openInputStream(Uri uri) { if (!URI_TO_INPUT_STREAMS.containsKey(uri)) { throw new IllegalArgumentException( "You must first register an InputStream for uri: " + uri); } return URI_TO_INPUT_STREAMS.get(uri); } @Implementation public AssetFileDescriptor openAssetFileDescriptor(Uri uri, String type) { if (!URI_TO_FILE_DESCRIPTOR.containsKey(uri)) { throw new IllegalArgumentException( "You must first register an AssetFileDescriptor for " + "uri: " + uri); } return URI_TO_FILE_DESCRIPTOR.get(uri); } } @Implements(Bitmap.class) public static class MutableShadowBitmap extends ShadowBitmap { @Implementation public static Bitmap createBitmap(int width, int height, Bitmap.Config config) { Bitmap bitmap = ShadowBitmap.createBitmap(width, height, config); Shadows.shadowOf(bitmap).setMutable(true); return bitmap; } } @Implements(MediaMetadataRetriever.class) public static class ShadowMediaMetadataRetriever { @Implementation @SuppressWarnings("unused") public Bitmap getFrameAtTime() { Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); Shadows.shadowOf(bitmap).appendDescription(" from MediaMetadataRetriever"); return bitmap; } } }