/*
* 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.bitmaps;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ConcurrentModificationException;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.soloader.SoLoaderShim;
import com.facebook.imagepipeline.memory.BitmapCounter;
import com.facebook.imagepipeline.memory.BitmapCounterProvider;
import com.facebook.imagepipeline.memory.PooledByteBuffer;
import com.facebook.imagepipeline.memory.SingleByteArrayPool;
import com.facebook.imagepipeline.nativecode.Bitmaps;
import com.facebook.imagepipeline.testing.MockBitmapFactory;
import com.facebook.testing.robolectric.v2.WithTestDefaultsRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
/**
* Tests for {@link DalvikBitmapFactory}.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@RunWith(WithTestDefaultsRunner.class)
@PrepareOnlyThisForTest({
BitmapCounterProvider.class,
BitmapFactory.class,
Bitmaps.class})
public class DalvikBitmapFactoryTest {
static {
SoLoaderShim.setInTestMode();
}
private static final int IMAGE_SIZE = 5;
private static final int LENGTH = 10;
private static final long POINTER = 1000L;
private static final int MAX_BITMAP_COUNT = 2;
private static final int MAX_BITMAP_SIZE =
MAX_BITMAP_COUNT * MockBitmapFactory.DEFAULT_BITMAP_SIZE;
private SingleByteArrayPool mSingleByteArrayPool;
private DalvikBitmapFactory mDalvikBitmapFactory;
private CloseableReference<PooledByteBuffer> mInputImageRef;
private byte[] mInputBuf;
private byte[] mDecodeBuf;
private Bitmap mBitmap;
private BitmapCounter mBitmapCounter;
@Before
public void setUp() {
mSingleByteArrayPool = mock(SingleByteArrayPool.class);
mBitmap = MockBitmapFactory.create();
mBitmapCounter = new BitmapCounter(MAX_BITMAP_COUNT, MAX_BITMAP_SIZE);
mockStatic(BitmapCounterProvider.class);
when(BitmapCounterProvider.get()).thenReturn(mBitmapCounter);
mockStatic(BitmapFactory.class);
when(BitmapFactory.decodeByteArray(
any(byte[].class),
anyInt(),
anyInt(),
any(BitmapFactory.Options.class)))
.thenReturn(mBitmap);
mInputBuf = new byte[LENGTH];
PooledByteBuffer input = new TrivialPooledByteBuffer(mInputBuf, POINTER);
mInputImageRef = CloseableReference.of(input);
mDecodeBuf = new byte[mInputBuf.length];
when(mSingleByteArrayPool.get(Integer.valueOf(mDecodeBuf.length))).thenReturn(mDecodeBuf);
mockStatic(Bitmaps.class);
mDalvikBitmapFactory = new DalvikBitmapFactory(
null,
mSingleByteArrayPool);
}
@Test
public void testDecode_Jpeg_Detailed() {
setUpJpegDecode();
CloseableReference<Bitmap> result = mDalvikBitmapFactory.decodeJPEGFromPooledByteBuffer(
mInputImageRef,
IMAGE_SIZE);
verifyDecodesJpeg(result);
}
@Test
public void testDecodeJpeg_incomplete() {
when(mSingleByteArrayPool.get(IMAGE_SIZE + 2)).thenReturn(mDecodeBuf);
CloseableReference<Bitmap> result =
mDalvikBitmapFactory.decodeJPEGFromPooledByteBuffer(mInputImageRef, IMAGE_SIZE);
verify(mSingleByteArrayPool).get(IMAGE_SIZE + 2);
verifyStatic();
BitmapFactory.decodeByteArray(
same(mDecodeBuf),
eq(0),
eq(IMAGE_SIZE + 2),
argThat(new BitmapFactoryOptionsMatcher()));
assertEquals((byte) 0xff, mDecodeBuf[5]);
assertEquals((byte) 0xd9, mDecodeBuf[6]);
assertEquals(1, mInputImageRef.getUnderlyingReferenceTestOnly().getRefCountTestOnly());
assertEquals(mBitmap, result.get());
assertTrue(result.isValid());
assertEquals(1, mBitmapCounter.getCount());
assertEquals(MockBitmapFactory.DEFAULT_BITMAP_SIZE, mBitmapCounter.getSize());
}
@Test(expected = TooManyBitmapsException.class)
public void testHitBitmapLimit_static() {
mBitmapCounter.increase(MockBitmapFactory.createForSize(MAX_BITMAP_SIZE));
try {
mDalvikBitmapFactory.decodeFromPooledByteBuffer(mInputImageRef);
} finally {
verify(mBitmap).recycle();
assertEquals(1, mBitmapCounter.getCount());
assertEquals(MAX_BITMAP_SIZE, mBitmapCounter.getSize());
}
}
@Test(expected = ConcurrentModificationException.class)
public void testPinBitmapFailure_static() {
PowerMockito.doThrow(new ConcurrentModificationException()).when(Bitmaps.class);
Bitmaps.pinBitmap(any(Bitmap.class));
try {
mDalvikBitmapFactory.decodeFromPooledByteBuffer(mInputImageRef);
} finally {
verify(mBitmap).recycle();
assertEquals(0, mBitmapCounter.getCount());
assertEquals(0, mBitmapCounter.getSize());
}
}
private void verifyDecodesStatic(CloseableReference<Bitmap> result) {
assertEquals(1, mInputImageRef.getUnderlyingReferenceTestOnly().getRefCountTestOnly());
assertEquals(mBitmap, result.get());
assertTrue(result.isValid());
assertEquals(1, mBitmapCounter.getCount());
assertEquals(MockBitmapFactory.DEFAULT_BITMAP_SIZE, mBitmapCounter.getSize());
verifyStatic();
Bitmaps.pinBitmap(mBitmap);
verify(mSingleByteArrayPool).release(mDecodeBuf);
}
private void setUpJpegDecode() {
mInputBuf[3] = (byte) 0xff;
mInputBuf[4] = (byte) 0xd9;
when(mSingleByteArrayPool.get(IMAGE_SIZE + 2)).thenReturn(mDecodeBuf);
}
private void verifyDecodesJpeg(CloseableReference<Bitmap> result) {
verify(mSingleByteArrayPool).get(IMAGE_SIZE + 2);
verifyStatic();
BitmapFactory.decodeByteArray(
same(mDecodeBuf),
eq(0),
eq(IMAGE_SIZE),
argThat(new BitmapFactoryOptionsMatcher()));
assertEquals(1, mInputImageRef.getUnderlyingReferenceTestOnly().getRefCountTestOnly());
assertEquals(mBitmap, result.get());
assertTrue(result.isValid());
assertEquals(1, mBitmapCounter.getCount());
assertEquals(MockBitmapFactory.DEFAULT_BITMAP_SIZE, mBitmapCounter.getSize());
}
private static class BitmapFactoryOptionsMatcher
extends ArgumentMatcher<BitmapFactory.Options> {
@Override
public boolean matches(Object argument) {
if (argument == null) {
return false;
}
BitmapFactory.Options options = (BitmapFactory.Options) argument;
return options.inDither &&
options.inPreferredConfig == Bitmaps.BITMAP_CONFIG &&
options.inPurgeable;
}
}
/**
* A trivial implementation of {@link PooledByteBuffer}
*/
private static class TrivialPooledByteBuffer implements PooledByteBuffer {
private byte[] mBuf;
private long mNativePtr;
public TrivialPooledByteBuffer(byte[] buf) {
this(buf, 0L);
}
public TrivialPooledByteBuffer(byte[] buf, long nativePtr) {
mBuf = buf;
mNativePtr = nativePtr;
}
@Override
public int size() {
return isClosed() ? -1 : mBuf.length;
}
@Override
public InputStream getStream() {
return new ByteArrayInputStream(mBuf);
}
@Override
public byte read(int offset) {
return mBuf[offset];
}
@Override
public long getNativePtr() {
return mNativePtr;
}
@Override
public boolean isClosed() {
return mBuf == null;
}
@Override
public void close() {
mBuf = null;
}
}
}