/* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import android.net.Uri; import android.view.Gravity; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.concurrent.FutureTask; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.shadows.ShadowBitmap; import org.robolectric.shadows.ShadowMatrix; import static android.graphics.Bitmap.Config.ARGB_8888; import static android.media.ExifInterface.ORIENTATION_FLIP_HORIZONTAL; import static android.media.ExifInterface.ORIENTATION_FLIP_VERTICAL; import static android.media.ExifInterface.ORIENTATION_ROTATE_90; import static android.media.ExifInterface.ORIENTATION_TRANSPOSE; import static android.media.ExifInterface.ORIENTATION_TRANSVERSE; import static com.squareup.picasso.BitmapHunter.forRequest; import static com.squareup.picasso.BitmapHunter.transformResult; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; import static com.squareup.picasso.Picasso.Priority.HIGH; import static com.squareup.picasso.Picasso.Priority.LOW; import static com.squareup.picasso.Picasso.Priority.NORMAL; import static com.squareup.picasso.TestUtils.ASSET_KEY_1; import static com.squareup.picasso.TestUtils.ASSET_URI_1; import static com.squareup.picasso.TestUtils.CONTACT_KEY_1; import static com.squareup.picasso.TestUtils.CONTACT_PHOTO_KEY_1; import static com.squareup.picasso.TestUtils.CONTACT_PHOTO_URI_1; import static com.squareup.picasso.TestUtils.CONTACT_URI_1; import static com.squareup.picasso.TestUtils.CONTENT_1_URL; import static com.squareup.picasso.TestUtils.CONTENT_KEY_1; import static com.squareup.picasso.TestUtils.CUSTOM_URI; import static com.squareup.picasso.TestUtils.CUSTOM_URI_KEY; import static com.squareup.picasso.TestUtils.FILE_1_URL; import static com.squareup.picasso.TestUtils.FILE_KEY_1; import static com.squareup.picasso.TestUtils.MEDIA_STORE_CONTENT_1_URL; import static com.squareup.picasso.TestUtils.MEDIA_STORE_CONTENT_KEY_1; import static com.squareup.picasso.TestUtils.RESOURCE_ID_1; import static com.squareup.picasso.TestUtils.RESOURCE_ID_KEY_1; import static com.squareup.picasso.TestUtils.RESOURCE_ID_URI; import static com.squareup.picasso.TestUtils.RESOURCE_ID_URI_KEY; import static com.squareup.picasso.TestUtils.RESOURCE_TYPE_URI; import static com.squareup.picasso.TestUtils.RESOURCE_TYPE_URI_KEY; import static com.squareup.picasso.TestUtils.URI_1; import static com.squareup.picasso.TestUtils.URI_KEY_1; import static com.squareup.picasso.TestUtils.makeBitmap; import static com.squareup.picasso.TestUtils.mockAction; import static com.squareup.picasso.TestUtils.mockImageViewTarget; import static com.squareup.picasso.TestUtils.mockPicasso; import static org.fest.assertions.api.ANDROID.assertThat; import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.entry; import static org.fest.assertions.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import static org.robolectric.Shadows.shadowOf; @RunWith(RobolectricGradleTestRunner.class) public class BitmapHunterTest { @Mock Context context; @Mock Picasso picasso; @Mock Cache cache; @Mock Stats stats; @Mock Dispatcher dispatcher; @Mock Downloader downloader; final Bitmap bitmap = makeBitmap(); @Before public void setUp() { initMocks(this); } @Test public void nullDecodeResponseIsError() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, null); hunter.run(); verify(dispatcher).dispatchFailed(hunter); } @Test public void runWithResultDispatchComplete() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, bitmap); hunter.run(); verify(dispatcher).dispatchComplete(hunter); } @Test public void runWithNoResultDispatchFailed() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action); hunter.run(); verify(dispatcher).dispatchFailed(hunter); } @Test public void responseExceptionDispatchFailed() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, null, new NetworkRequestHandler.ResponseException(0, 504)); hunter.run(); verify(dispatcher).dispatchFailed(hunter); } @Test public void outOfMemoryDispatchFailed() { when(stats.createSnapshot()).thenReturn(mock(StatsSnapshot.class)); Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new OOMBitmapHunter(picasso, dispatcher, cache, stats, action); try { hunter.run(); } catch (Throwable t) { Exception exception = hunter.getException(); verify(dispatcher).dispatchFailed(hunter); verify(stats).createSnapshot(); assertThat(hunter.getResult()).isNull(); assertThat(exception).isNotNull(); assertThat(exception.getCause()).isInstanceOf(OutOfMemoryError.class); } } @Test public void runWithIoExceptionDispatchRetry() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, null, new IOException()); hunter.run(); verify(dispatcher).dispatchRetry(hunter); } @Test public void huntDecodesWhenNotInCache() throws Exception { Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); TestableBitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, bitmap); Bitmap result = hunter.hunt(); verify(cache).get(URI_KEY_1); verify(hunter.requestHandler).load(action.getRequest(), 0); assertThat(result).isEqualTo(bitmap); } @Test public void huntReturnsWhenResultInCache() throws Exception { when(cache.get(URI_KEY_1)).thenReturn(bitmap); Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); TestableBitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action, bitmap); Bitmap result = hunter.hunt(); verify(cache).get(URI_KEY_1); verify(hunter.requestHandler, never()).load(action.getRequest(), 0); assertThat(result).isEqualTo(bitmap); } @Test public void huntUnrecognizedUri() throws Exception { Action action = mockAction(CUSTOM_URI_KEY, CUSTOM_URI); BitmapHunter hunter = forRequest(picasso, dispatcher, cache, stats, action); try { hunter.hunt(); fail("Unrecognized URI should throw exception."); } catch (IllegalStateException ignored) { } } @Test public void huntDecodesWithRequestHandler() throws Exception { Action action = mockAction(CUSTOM_URI_KEY, CUSTOM_URI); BitmapHunter hunter = forRequest(mockPicasso(new CustomRequestHandler()), dispatcher, cache, stats, action); Bitmap result = hunter.hunt(); assertThat(result).isEqualTo(bitmap); } @Test public void attachSingleRequest() { Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action1); assertThat(hunter.action).isEqualTo(action1); hunter.detach(action1); hunter.attach(action1); assertThat(hunter.action).isEqualTo(action1); assertThat(hunter.actions).isNull(); } @Test public void attachMultipleRequests() { Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); Action action2 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action1); assertThat(hunter.actions).isNull(); hunter.attach(action2); assertThat(hunter.actions).isNotNull().hasSize(1); } @Test public void detachSingleRequest() { Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action); assertThat(hunter.action).isNotNull(); hunter.detach(action); assertThat(hunter.action).isNull(); } @Test public void detachMultipleRequests() { Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); Action action2 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action); hunter.attach(action2); hunter.detach(action2); assertThat(hunter.action).isNotNull(); assertThat(hunter.actions).isNotNull().isEmpty(); hunter.detach(action); assertThat(hunter.action).isNull(); } @Test public void cancelSingleRequest() { Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action1); hunter.future = new FutureTask<>(mock(Runnable.class), mock(Object.class)); assertThat(hunter.isCancelled()).isFalse(); assertThat(hunter.cancel()).isFalse(); hunter.detach(action1); assertThat(hunter.cancel()).isTrue(); assertThat(hunter.isCancelled()).isTrue(); } @Test public void cancelMultipleRequests() { Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); Action action2 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget()); BitmapHunter hunter = new TestableBitmapHunter(picasso, dispatcher, cache, stats, action1); hunter.future = new FutureTask<>(mock(Runnable.class), mock(Object.class)); hunter.attach(action2); assertThat(hunter.isCancelled()).isFalse(); assertThat(hunter.cancel()).isFalse(); hunter.detach(action1); hunter.detach(action2); assertThat(hunter.cancel()).isTrue(); assertThat(hunter.isCancelled()).isTrue(); } // --------------------------------------- @Test public void forContentProviderRequest() { Action action = mockAction(CONTENT_KEY_1, CONTENT_1_URL); BitmapHunter hunter = forRequest(mockPicasso(new ContentStreamRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ContentStreamRequestHandler.class); } @Test public void forMediaStoreRequest() { Action action = mockAction(MEDIA_STORE_CONTENT_KEY_1, MEDIA_STORE_CONTENT_1_URL); BitmapHunter hunter = forRequest(mockPicasso(new MediaStoreRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(MediaStoreRequestHandler.class); } @Test public void forContactsPhotoRequest() { Action action = mockAction(CONTACT_KEY_1, CONTACT_URI_1); BitmapHunter hunter = forRequest(mockPicasso(new ContactsPhotoRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ContactsPhotoRequestHandler.class); } @Test public void forContactsThumbnailPhotoRequest() { Action action = mockAction(CONTACT_PHOTO_KEY_1, CONTACT_PHOTO_URI_1); BitmapHunter hunter = forRequest(mockPicasso(new ContactsPhotoRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ContactsPhotoRequestHandler.class); } @Test public void forNetworkRequest() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = forRequest(mockPicasso(new NetworkRequestHandler(downloader, stats)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(NetworkRequestHandler.class); } @Test public void forFileWithAuthorityRequest() { Action action = mockAction(FILE_KEY_1, FILE_1_URL); BitmapHunter hunter = forRequest(mockPicasso(new FileRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(FileRequestHandler.class); } @Test public void forAndroidResourceRequest() { Action action = mockAction(RESOURCE_ID_KEY_1, null, null, RESOURCE_ID_1); BitmapHunter hunter = forRequest(mockPicasso(new ResourceRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ResourceRequestHandler.class); } @Test public void forAndroidResourceUriWithId() { Action action = mockAction(RESOURCE_ID_URI_KEY, RESOURCE_ID_URI); BitmapHunter hunter = forRequest(mockPicasso(new ResourceRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ResourceRequestHandler.class); } @Test public void forAndroidResourceUriWithType() { Action action = mockAction(RESOURCE_TYPE_URI_KEY, RESOURCE_TYPE_URI); BitmapHunter hunter = forRequest(mockPicasso(new ResourceRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(ResourceRequestHandler.class); } @Test public void forAssetRequest() { Action action = mockAction(ASSET_KEY_1, ASSET_URI_1); BitmapHunter hunter = forRequest(mockPicasso(new AssetRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(AssetRequestHandler.class); } @Test public void forFileWithNoPathSegments() { Action action = mockAction("keykeykey", Uri.fromFile(new File("/"))); BitmapHunter hunter = forRequest(mockPicasso(new FileRequestHandler(context)), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(FileRequestHandler.class); } @Test public void forCustomRequest() { Action action = mockAction(CUSTOM_URI_KEY, CUSTOM_URI); BitmapHunter hunter = forRequest(mockPicasso(new CustomRequestHandler()), dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isInstanceOf(CustomRequestHandler.class); } @Test public void forOverrideRequest() { Action action = mockAction(ASSET_KEY_1, ASSET_URI_1); RequestHandler handler = new AssetRequestHandler(context); List<RequestHandler> handlers = Collections.singletonList(handler); // Must use non-mock constructor because that is where Picasso's list of handlers is created. Picasso picasso = new Picasso(context, dispatcher, cache, null, null, handlers, stats, ARGB_8888, false, false); BitmapHunter hunter = forRequest(picasso, dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isEqualTo(handler); } @Test public void sequenceIsIncremented() { Action action = mockAction(URI_KEY_1, URI_1); Picasso picasso = mockPicasso(); BitmapHunter hunter1 = forRequest(picasso, dispatcher, cache, stats, action); BitmapHunter hunter2 = forRequest(picasso, dispatcher, cache, stats, action); assertThat(hunter2.sequence).isGreaterThan(hunter1.sequence); } @Test public void getPriorityWithNoRequests() { Action action = mockAction(URI_KEY_1, URI_1); BitmapHunter hunter = forRequest(mockPicasso(new NetworkRequestHandler(downloader, stats)), dispatcher, cache, stats, action); hunter.detach(action); assertThat(hunter.getAction()).isNull(); assertThat(hunter.getActions()).isNull(); assertThat(hunter.getPriority()).isEqualTo(LOW); } @Test public void getPriorityWithSingleRequest() { Action action = mockAction(URI_KEY_1, URI_1, HIGH); BitmapHunter hunter = forRequest(mockPicasso(new NetworkRequestHandler(downloader, stats)), dispatcher, cache, stats, action); assertThat(hunter.getAction()).isEqualTo(action); assertThat(hunter.getActions()).isNull(); assertThat(hunter.getPriority()).isEqualTo(HIGH); } @Test public void getPriorityWithMultipleRequests() { Action action1 = mockAction(URI_KEY_1, URI_1, NORMAL); Action action2 = mockAction(URI_KEY_1, URI_1, HIGH); BitmapHunter hunter = forRequest(mockPicasso(new NetworkRequestHandler(downloader, stats)), dispatcher, cache, stats, action1); hunter.attach(action2); assertThat(hunter.getAction()).isEqualTo(action1); assertThat(hunter.getActions()).hasSize(1).contains(action2); assertThat(hunter.getPriority()).isEqualTo(HIGH); } @Test public void getPriorityAfterDetach() { Action action1 = mockAction(URI_KEY_1, URI_1, NORMAL); Action action2 = mockAction(URI_KEY_1, URI_1, HIGH); BitmapHunter hunter = forRequest(mockPicasso(new NetworkRequestHandler(downloader, stats)), dispatcher, cache, stats, action1); hunter.attach(action2); assertThat(hunter.getAction()).isEqualTo(action1); assertThat(hunter.getActions()).hasSize(1).contains(action2); assertThat(hunter.getPriority()).isEqualTo(HIGH); hunter.detach(action2); assertThat(hunter.getAction()).isEqualTo(action1); assertThat(hunter.getActions()).isEmpty(); assertThat(hunter.getPriority()).isEqualTo(NORMAL); } @Test public void exifRotation() { Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_ROTATE_90); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("rotate 90.0"); } @Test public void exifRotationSizing() throws Exception { Request data = new Request.Builder(URI_1).resize(5, 10).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_ROTATE_90); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).contains("scale 1.0 0.5"); } @Test public void exifRotationNoSizing() throws Exception { Request data = new Request.Builder(URI_1).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_ROTATE_90); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).contains("rotate 90.0"); } @Test public void rotation90Sizing() throws Exception { Request data = new Request.Builder(URI_1).rotate(90).resize(5, 10).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).contains("scale 1.0 0.5"); } @Test public void rotation180Sizing() throws Exception { Request data = new Request.Builder(URI_1).rotate(180).resize(5, 10).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).contains("scale 0.5 1.0"); } @Test public void rotation90WithPivotSizing() throws Exception { Request data = new Request.Builder(URI_1).rotate(90,0,10).resize(5, 10).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).contains("scale 1.0 0.5"); } @Test public void exifVerticalFlip() { Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_FLIP_VERTICAL); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPostOperations()).containsOnly("scale -1.0 1.0"); assertThat(shadowMatrix.getPreOperations()).containsOnly("rotate 180.0"); } @Test public void exifHorizontalFlip() { Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_FLIP_HORIZONTAL); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPostOperations()).containsOnly("scale -1.0 1.0"); assertThat(shadowMatrix.getPreOperations()).doesNotContain("rotate 180.0"); assertThat(shadowMatrix.getPreOperations()).doesNotContain("rotate 90.0"); assertThat(shadowMatrix.getPreOperations()).doesNotContain("rotate 270.0"); } @Test public void exifTranspose() { Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_TRANSPOSE); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPostOperations()).containsOnly("scale -1.0 1.0"); assertThat(shadowMatrix.getPreOperations()).containsOnly("rotate 90.0"); } @Test public void exifTransverse() { Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, ORIENTATION_TRANSVERSE); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPostOperations()).containsOnly("scale -1.0 1.0"); assertThat(shadowMatrix.getPreOperations()).containsOnly("rotate 270.0"); } @Test public void keepsAspectRationWhileResizingWhenDesiredWidthIs0() { Request request = new Request.Builder(URI_1).resize(20, 0).build(); Bitmap source = Bitmap.createBitmap(40, 20, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void keepsAspectRationWhileResizingWhenDesiredHeightIs0() { Request request = new Request.Builder(URI_1).resize(0, 10).build(); Bitmap source = Bitmap.createBitmap(40, 20, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void centerCropResultMatchesTargetSize() { Request request = new Request.Builder(URI_1).resize(1080, 642).centerCrop().build(); Bitmap source = Bitmap.createBitmap(640, 640, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); String scalePreOperation = shadowMatrix.getPreOperations().get(0); assertThat(scalePreOperation).startsWith("scale "); float scaleX = Float.valueOf(scalePreOperation.split(" ")[1]); float scaleY = Float.valueOf(scalePreOperation.split(" ")[2]); int transformedWidth = Math.round(result.getWidth() * scaleX); int transformedHeight = Math.round(result.getHeight() * scaleY); assertThat(transformedWidth).isEqualTo(1080); assertThat(transformedHeight).isEqualTo(642); } @Test public void centerCropResultMatchesTargetSizeWhileDesiredWidthIs0() { Request request = new Request.Builder(URI_1).resize(0, 642).centerCrop().build(); Bitmap source = Bitmap.createBitmap(640, 640, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); String scalePreOperation = shadowMatrix.getPreOperations().get(0); assertThat(scalePreOperation).startsWith("scale "); float scaleX = Float.valueOf(scalePreOperation.split(" ")[1]); float scaleY = Float.valueOf(scalePreOperation.split(" ")[2]); int transformedWidth = Math.round(result.getWidth() * scaleX); int transformedHeight = Math.round(result.getHeight() * scaleY); assertThat(transformedWidth).isEqualTo(642); assertThat(transformedHeight).isEqualTo(642); } @Test public void centerCropResultMatchesTargetSizeWhileDesiredHeightIs0() { Request request = new Request.Builder(URI_1).resize(1080, 0).centerCrop().build(); Bitmap source = Bitmap.createBitmap(640, 640, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); String scalePreOperation = shadowMatrix.getPreOperations().get(0); assertThat(scalePreOperation).startsWith("scale "); float scaleX = Float.valueOf(scalePreOperation.split(" ")[1]); float scaleY = Float.valueOf(scalePreOperation.split(" ")[2]); int transformedWidth = Math.round(result.getWidth() * scaleX); int transformedHeight = Math.round(result.getHeight() * scaleY); assertThat(transformedWidth).isEqualTo(1080); assertThat(transformedHeight).isEqualTo(1080); } @Test public void centerInsideResultMatchesTargetSizeWhileDesiredWidthIs0() { Request request = new Request.Builder(URI_1).resize(0, 642).centerInside().build(); Bitmap source = Bitmap.createBitmap(640, 640, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); String scalePreOperation = shadowMatrix.getPreOperations().get(0); assertThat(scalePreOperation).startsWith("scale "); float scaleX = Float.valueOf(scalePreOperation.split(" ")[1]); float scaleY = Float.valueOf(scalePreOperation.split(" ")[2]); int transformedWidth = Math.round(result.getWidth() * scaleX); int transformedHeight = Math.round(result.getHeight() * scaleY); assertThat(transformedWidth).isEqualTo(642); assertThat(transformedHeight).isEqualTo(642); } @Test public void centerInsideResultMatchesTargetSizeWhileDesiredHeightIs0() { Request request = new Request.Builder(URI_1).resize(1080, 0).centerInside().build(); Bitmap source = Bitmap.createBitmap(640, 640, ARGB_8888); Bitmap result = transformResult(request, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); String scalePreOperation = shadowMatrix.getPreOperations().get(0); assertThat(scalePreOperation).startsWith("scale "); float scaleX = Float.valueOf(scalePreOperation.split(" ")[1]); float scaleY = Float.valueOf(scalePreOperation.split(" ")[2]); int transformedWidth = Math.round(result.getWidth() * scaleX); int transformedHeight = Math.round(result.getHeight() * scaleY); assertThat(transformedWidth).isEqualTo(1080); assertThat(transformedHeight).isEqualTo(1080); } @Test public void exifRotationWithManualRotation() { Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap result = transformResult(data, source, ORIENTATION_ROTATE_90); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("rotate 90.0"); assertThat(shadowMatrix.getSetOperations()).contains(entry("rotate", "-45.0")); } @Test public void rotation() { Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Request data = new Request.Builder(URI_1).rotate(-45).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getSetOperations()).contains(entry("rotate", "-45.0")); } @Test public void pivotRotation() { Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Request data = new Request.Builder(URI_1).rotate(-45, 10, 10).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getSetOperations()).contains(entry("rotate", "-45.0 10.0 10.0")); } @Test public void resize() { Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Request data = new Request.Builder(URI_1).resize(20, 15).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 2.0 1.5"); } @Test public void centerCropTallTooSmall() { Bitmap source = Bitmap.createBitmap(10, 20, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(5); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropTallTooLarge() { Bitmap source = Bitmap.createBitmap(100, 200, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerCrop().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(50); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(100); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(100); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void centerCropWideTooSmall() { Bitmap source = Bitmap.createBitmap(20, 10, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(5); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropWithGravityHorizontalLeft() { Bitmap source = Bitmap.createBitmap(20, 10, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop(Gravity.LEFT).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropWithGravityHorizontalRight() { Bitmap source = Bitmap.createBitmap(20, 10, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop(Gravity.RIGHT).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropWithGravityVerticalTop() { Bitmap source = Bitmap.createBitmap(10, 20, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop(Gravity.TOP).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropWithGravityVerticalBottom() { Bitmap source = Bitmap.createBitmap(10, 20, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 40).centerCrop(Gravity.BOTTOM).build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(10); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(10); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 4.0 4.0"); } @Test public void centerCropWideTooLarge() { Bitmap source = Bitmap.createBitmap(200, 100, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerCrop().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); assertThat(shadowBitmap.getCreatedFromX()).isEqualTo(50); assertThat(shadowBitmap.getCreatedFromY()).isEqualTo(0); assertThat(shadowBitmap.getCreatedFromWidth()).isEqualTo(100); assertThat(shadowBitmap.getCreatedFromHeight()).isEqualTo(100); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void centerInsideTallTooSmall() { Bitmap source = Bitmap.createBitmap(20, 10, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerInside().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 2.5 2.5"); } @Test public void centerInsideTallTooLarge() { Bitmap source = Bitmap.createBitmap(100, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerInside().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void centerInsideWideTooSmall() { Bitmap source = Bitmap.createBitmap(10, 20, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerInside().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 2.5 2.5"); } @Test public void centerInsideWideTooLarge() { Bitmap source = Bitmap.createBitmap(50, 100, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).centerInside().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void onlyScaleDownOriginalBigger() { Bitmap source = Bitmap.createBitmap(100, 100, ARGB_8888); Request data = new Request.Builder(URI_1).resize(50, 50).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.5 0.5"); } @Test public void onlyScaleDownOriginalSmaller() { Bitmap source = Bitmap.createBitmap(50, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(100, 100).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); assertThat(result).isSameAs(source); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isNull(); assertThat(shadowBitmap.getCreatedFromBitmap()).isNotSameAs(source); } @Test public void onlyScaleDownOriginalSmallerWidthIs0() { Bitmap source = Bitmap.createBitmap(50, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(0, 60).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); assertThat(result).isSameAs(source); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isNull(); } @Test public void onlyScaleDownOriginalSmallerHeightIs0() { Bitmap source = Bitmap.createBitmap(50, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(60, 0).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); assertThat(result).isSameAs(source); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isNull(); } @Test public void onlyScaleDownOriginalBiggerWidthIs0() { Bitmap source = Bitmap.createBitmap(50, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(0, 40).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.8 0.8"); } @Test public void onlyScaleDownOriginalBiggerHeightIs0() { Bitmap source = Bitmap.createBitmap(50, 50, ARGB_8888); Request data = new Request.Builder(URI_1).resize(40, 0).onlyScaleDown().build(); Bitmap result = transformResult(data, source, 0); ShadowBitmap shadowBitmap = shadowOf(result); assertThat(shadowBitmap.getCreatedFromBitmap()).isSameAs(source); Matrix matrix = shadowBitmap.getCreatedFromMatrix(); ShadowMatrix shadowMatrix = shadowOf(matrix); assertThat(shadowMatrix.getPreOperations()).containsOnly("scale 0.8 0.8"); } @Test public void reusedBitmapIsNotRecycled() { Request data = new Request.Builder(URI_1).build(); Bitmap source = Bitmap.createBitmap(10, 10, ARGB_8888); Bitmap result = transformResult(data, source, 0); assertThat(result).isSameAs(source).isNotRecycled(); } @Test public void crashingOnTransformationThrows() { Transformation badTransformation = new Transformation() { @Override public Bitmap transform(Bitmap source) { throw new NullPointerException("hello"); } @Override public String key() { return "test"; } }; List<Transformation> transformations = Collections.singletonList(badTransformation); Bitmap original = Bitmap.createBitmap(10, 10, ARGB_8888); try { BitmapHunter.applyCustomTransformations(transformations, original); fail("Expected exception to be thrown."); } catch (RuntimeException e) { assertThat(e).hasMessage("Transformation " + badTransformation.key() + " crashed with exception."); } } @Test public void nullResultFromTransformationThrows() { Transformation badTransformation = new Transformation() { @Override public Bitmap transform(Bitmap source) { return null; } @Override public String key() { return "test"; } }; List<Transformation> transformations = Collections.singletonList(badTransformation); Bitmap original = Bitmap.createBitmap(10, 10, ARGB_8888); try { BitmapHunter.applyCustomTransformations(transformations, original); fail("Expected exception to be thrown."); } catch (RuntimeException e) { assertThat(e).hasMessageContaining( "Transformation " + badTransformation.key() + " returned null"); } } @Test public void doesNotRecycleOriginalTransformationThrows() { Transformation badTransformation = new Transformation() { @Override public Bitmap transform(Bitmap source) { // Should recycle source. return Bitmap.createBitmap(10, 10, ARGB_8888); } @Override public String key() { return "test"; } }; List<Transformation> transformations = Collections.singletonList(badTransformation); Bitmap original = Bitmap.createBitmap(10, 10, ARGB_8888); try { BitmapHunter.applyCustomTransformations(transformations, original); fail("Expected exception to be thrown."); } catch (RuntimeException e) { assertThat(e).hasMessage("Transformation " + badTransformation.key() + " mutated input Bitmap but failed to recycle the original."); } } @Test public void recycledOriginalTransformationThrows() { Transformation badTransformation = new Transformation() { @Override public Bitmap transform(Bitmap source) { source.recycle(); return source; } @Override public String key() { return "test"; } }; List<Transformation> transformations = Collections.singletonList(badTransformation); Bitmap original = Bitmap.createBitmap(10, 10, ARGB_8888); try { BitmapHunter.applyCustomTransformations(transformations, original); fail("Expected exception to be thrown."); } catch (RuntimeException e) { assertThat(e).hasMessage("Transformation " + badTransformation.key() + " returned input Bitmap but recycled it."); } } private static class TestableBitmapHunter extends BitmapHunter { TestableBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { this(picasso, dispatcher, cache, stats, action, null); } TestableBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, Bitmap result) { this(picasso, dispatcher, cache, stats, action, result, null); } TestableBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, Bitmap result, IOException exception) { super(picasso, dispatcher, cache, stats, action, spy(new TestableRequestHandler(result, exception))); } @Override Picasso.LoadedFrom getLoadedFrom() { return MEMORY; } } private static class TestableRequestHandler extends RequestHandler { private final Bitmap bitmap; private final IOException exception; TestableRequestHandler(Bitmap bitmap, IOException exception) { this.bitmap = bitmap; this.exception = exception; } @Override public boolean canHandleRequest(Request data) { return true; } @Override public Result load(Request request, int networkPolicy) throws IOException { if (exception != null) { throw exception; } return new Result(bitmap, MEMORY); } @Override int getRetryCount() { return 1; } } private static class OOMBitmapHunter extends BitmapHunter { OOMBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { super(picasso, dispatcher, cache, stats, action, spy(new OOMRequestHandler())); } } private static class OOMRequestHandler extends TestableRequestHandler { OOMRequestHandler() { super(null, null); } @Override public Result load(Request request, int networkPolicy) throws IOException { throw new OutOfMemoryError(); } } private class CustomRequestHandler extends RequestHandler { @Override public boolean canHandleRequest(Request data) { return CUSTOM_URI.getScheme().equals(data.uri.getScheme()); } @Override public Result load(Request request, int networkPolicy) { return new Result(bitmap, MEMORY); } } }