diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java index b7477105..dd22d5c5 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java @@ -8,6 +8,9 @@ import android.graphics.Color; import com.otaliastudios.cameraview.internal.utils.Op; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -15,6 +18,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.Charset; import static org.mockito.Mockito.*; import static org.junit.Assert.*; @@ -28,44 +36,93 @@ public class CameraUtilsTest extends BaseTest { Context context = mock(Context.class); PackageManager pm = mock(PackageManager.class); when(context.getPackageManager()).thenReturn(pm); - when(pm.hasSystemFeature(anyString())).thenReturn(true); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)).thenReturn(true); assertTrue(CameraUtils.hasCameras(context)); - - when(pm.hasSystemFeature(anyString())).thenReturn(false); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(false); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)).thenReturn(true); + assertTrue(CameraUtils.hasCameras(context)); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(false); + when(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)).thenReturn(false); assertFalse(CameraUtils.hasCameras(context)); } - // Encodes bitmap and decodes again using our utility. - private Op encodeDecodeTask(Bitmap source) { - return encodeDecodeTask(source, 0, 0); + @NonNull + private Op writeAndReadString(@NonNull String data) { + final File file = new File(getContext().getFilesDir(), "string.txt"); + final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final Op result = new Op<>(true); + final FileCallback callback = new FileCallback() { + @Override + public void onFileReady(@Nullable File file) { + if (file == null) { + result.end(null); + } else { + // Read back the file. + try { + FileInputStream stream = new FileInputStream(file); + byte[] bytes = new byte[stream.available()]; + stream.read(bytes); + result.end(new String(bytes, Charset.forName("UTF-8"))); + } catch (IOException e) { + result.end(null); + } + } + } + }; + uiSync(new Runnable() { + @Override + public void run() { + CameraUtils.writeToFile(bytes, file, callback); + } + }); + return result; + } + + @Test + public void testWriteToFile() { + Op op = writeAndReadString("testString"); + String result = op.await(2000); + assertEquals("testString", result); } + // Encodes bitmap and decodes again using our utility. - private Op encodeDecodeTask(Bitmap source, final int maxWidth, final int maxHeight) { + private Op encodeDecodeTask(@NonNull Bitmap source, final int maxWidth, final int maxHeight, boolean async) { final ByteArrayOutputStream os = new ByteArrayOutputStream(); // Using lossy JPG we can't have strict comparison of values after compression. source.compress(Bitmap.CompressFormat.PNG, 100, os); final byte[] data = os.toByteArray(); final Op decode = new Op<>(true); - final BitmapCallback callback = new BitmapCallback() { - @Override - public void onBitmapReady(Bitmap bitmap) { - decode.end(bitmap); - } - }; - - // Run on ui because it involves handlers. - uiSync(new Runnable() { - @Override - public void run() { - if (maxWidth > 0 && maxHeight > 0) { - CameraUtils.decodeBitmap(data, maxWidth, maxHeight, callback); - } else { - CameraUtils.decodeBitmap(data, callback); + if (async) { + final BitmapCallback callback = new BitmapCallback() { + @Override + public void onBitmapReady(Bitmap bitmap) { + decode.end(bitmap); + } + }; + + // Run on ui because it involves handlers. + uiSync(new Runnable() { + @Override + public void run() { + if (maxWidth > 0 && maxHeight > 0) { + CameraUtils.decodeBitmap(data, maxWidth, maxHeight, callback); + } else { + CameraUtils.decodeBitmap(data, callback); + } } + }); + } else { + Bitmap result; + if (maxWidth > 0 && maxHeight > 0) { + result = CameraUtils.decodeBitmap(data, maxWidth, maxHeight); + } else { + result = CameraUtils.decodeBitmap(data); } - }); + decode.end(result); + } return decode; } @@ -75,7 +132,24 @@ public class CameraUtilsTest extends BaseTest { Bitmap source = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); source.setPixel(0, 0, color); - Op decode = encodeDecodeTask(source); + Op decode = encodeDecodeTask(source, 0, 0, true); + Bitmap other = decode.await(800); + assertNotNull(other); + assertEquals(100, w); + assertEquals(200, h); + assertEquals(color, other.getPixel(0, 0)); + assertEquals(0, other.getPixel(0, h-1)); + assertEquals(0, other.getPixel(w-1, 0)); + assertEquals(0, other.getPixel(w-1, h-1)); + } + + @Test + public void testDecodeBitmapSync() { + int w = 100, h = 200, color = Color.WHITE; + Bitmap source = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + source.setPixel(0, 0, color); + + Op decode = encodeDecodeTask(source, 0, 0, false); Bitmap other = decode.await(800); assertNotNull(other); assertEquals(100, w); @@ -94,19 +168,19 @@ public class CameraUtilsTest extends BaseTest { Op op; Bitmap other; - op = encodeDecodeTask(source, 100, 100); + op = encodeDecodeTask(source, 100, 100, true); other = op.await(800); assertNotNull(other); assertTrue(other.getWidth() <= 100); assertTrue(other.getHeight() <= 100); - op = encodeDecodeTask(source, Integer.MAX_VALUE, Integer.MAX_VALUE); + op = encodeDecodeTask(source, Integer.MAX_VALUE, Integer.MAX_VALUE, true); other = op.await(800); assertNotNull(other); assertEquals(other.getWidth(), width); assertEquals(other.getHeight(), height); - op = encodeDecodeTask(source, 6000, 6000); + op = encodeDecodeTask(source, 6000, 6000, true); other = op.await(800); assertNotNull(other); assertEquals(other.getWidth(), width); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/size/SizeSelectorParserTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/size/SizeSelectorParserTest.java new file mode 100644 index 00000000..34047923 --- /dev/null +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/size/SizeSelectorParserTest.java @@ -0,0 +1,211 @@ +package com.otaliastudios.cameraview.size; + + +import android.content.res.TypedArray; + +import androidx.annotation.NonNull; +import androidx.annotation.StyleableRes; +import androidx.arch.core.util.Function; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SizeSelectorParserTest extends BaseTest { + + private MockTypedArray input; + private List sizes = Arrays.asList( + new Size(100, 200), + new Size(150, 300), + new Size(600, 900), + new Size(600, 600), + new Size(1600, 900), + new Size(30, 40), + new Size(40, 30), + new Size(2000, 4000) + ); + + @Before + public void setUp() { + input = new MockTypedArray(); + } + + @After + public void tearDown() { + input = null; + } + + private void doAssert(@NonNull Function, Void> assertions) { + SizeSelectorParser parser = new SizeSelectorParser(input.array); + assertions.apply(parser.getPictureSizeSelector().select(sizes)); + assertions.apply(parser.getVideoSizeSelector().select(sizes)); + } + + @Test + public void testWidth() { + input.setMinWidth(1500); + input.setMaxWidth(1700); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(1, input.size()); + assertEquals(new Size(1600, 900), input.get(0)); + return null; + } + }); + } + + @Test + public void testHeight() { + input.setMinHeight(25); + input.setMaxHeight(35); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(1, input.size()); + assertEquals(new Size(40, 30), input.get(0)); + return null; + } + }); + } + + @Test + public void testArea() { + input.setMinArea(30 * 30); + input.setMaxArea(40 * 40); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(2, input.size()); + assertTrue(input.contains(new Size(40, 30))); + assertTrue(input.contains(new Size(30, 40))); + return null; + } + }); + } + + @Test + public void testSmallest() { + input.setSmallest(true); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(sizes.size(), input.size()); + Size first = input.get(0); + assertEquals(30 * 40, first.getWidth() * first.getHeight()); + return null; + } + }); + } + + @Test + public void testBiggest() { + input.setBiggest(true); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(sizes.size(), input.size()); + assertEquals(new Size(2000, 4000), input.get(0)); + return null; + } + }); + } + + @Test + public void testAspectRatio() { + input.setAspectRatio("16:9"); + doAssert(new Function, Void>() { + @Override + public Void apply(List input) { + assertEquals(1, input.size()); + assertEquals(new Size(1600, 900), input.get(0)); + return null; + } + }); + } + + @SuppressWarnings("SameParameterValue") + private class MockTypedArray { + private TypedArray array = mock(TypedArray.class); + + private void setIntValue(@StyleableRes int index, int value) { + when(array.hasValue(index)).thenReturn(true); + when(array.getInteger(eq(index), anyInt())).thenReturn(value); + } + + private void setBooleanValue(@StyleableRes int index, boolean value) { + when(array.hasValue(index)).thenReturn(true); + when(array.getBoolean(eq(index), anyBoolean())).thenReturn(value); + } + + private void setStringValue(@StyleableRes int index, @NonNull String value) { + when(array.hasValue(index)).thenReturn(true); + when(array.getString(index)).thenReturn(value); + } + + private void setMinWidth(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMinWidth, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMinWidth, value); + } + + private void setMaxWidth(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMaxWidth, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMaxWidth, value); + } + + private void setMinHeight(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMinHeight, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMinHeight, value); + } + + private void setMaxHeight(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMaxHeight, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMaxHeight, value); + } + + private void setMinArea(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMinArea, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMinArea, value); + } + + private void setMaxArea(int value) { + setIntValue(R.styleable.CameraView_cameraPictureSizeMaxArea, value); + setIntValue(R.styleable.CameraView_cameraVideoSizeMaxArea, value); + } + + private void setSmallest(boolean value) { + setBooleanValue(R.styleable.CameraView_cameraPictureSizeSmallest, value); + setBooleanValue(R.styleable.CameraView_cameraVideoSizeSmallest, value); + } + + private void setBiggest(boolean value) { + setBooleanValue(R.styleable.CameraView_cameraPictureSizeBiggest, value); + setBooleanValue(R.styleable.CameraView_cameraVideoSizeBiggest, value); + } + + private void setAspectRatio(@NonNull String value) { + setStringValue(R.styleable.CameraView_cameraPictureSizeAspectRatio, value); + setStringValue(R.styleable.CameraView_cameraVideoSizeAspectRatio, value); + } + } +} diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index 1b81d370..9057e4d6 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -174,20 +174,23 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis // This can happen if picture was taken with a gesture. long callbackTime = System.currentTimeMillis(); if (mCaptureTime == 0) mCaptureTime = callbackTime - 300; + LOG.w("onPictureTaken called! Launching activity. Delay:", callbackTime - mCaptureTime); PicturePreviewActivity.setPictureResult(result); Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class); intent.putExtra("delay", callbackTime - mCaptureTime); - LOG.w("Picture delay:", callbackTime - mCaptureTime); startActivity(intent); mCaptureTime = 0; + LOG.w("onPictureTaken called! Launched activity."); } @Override public void onVideoTaken(@NonNull VideoResult result) { super.onVideoTaken(result); + LOG.w("onVideoTaken called! Launching activity."); VideoPreviewActivity.setVideoResult(result); Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class); startActivity(intent); + LOG.w("onVideoTaken called! Launched activity."); } @Override