diff --git a/.travis.yml b/.travis.yml
index d5cce1f7..410e1a0f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,6 +10,12 @@ sudo: false
jdk:
- oraclejdk8
+env:
+ global:
+ # Run android tests on api level 22
+ - EMULATOR_API=22
+ - EMULATOR_ABI=armeabi-v7a
+
android:
components:
- tools
@@ -17,9 +23,22 @@ android:
- build-tools-26.0.1
- android-26
- doc-26
+ # Android tests
+ - android-$EMULATOR_API
+ - sys-img-$EMULATOR_ABI-android-$EMULATOR_API
+ # sys-img-x86-google_apis-26
+
+before_script:
+ - echo no | android create avd --force --name test --target android-$EMULATOR_API --abi $EMULATOR_ABI
+ - emulator -avd test -no-audio -no-window &
+ - android-wait-for-emulator
+ - adb shell input keyevent 82 &
script:
- - ./gradlew clean connectedCheck testDebugUnitTest
+ - ./gradlew clean testDebugUnitTest connectedCheck coverageReport
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
cache:
directories:
diff --git a/README.md b/README.md
index a5aad9b9..6997c48c 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
+[![Build Status](https://travis-ci.org/natario1/CameraView.svg?branch=master)](https://travis-ci.org/natario1/CameraView)
+[![Code Coverage](https://codecov.io/gh/natario1/CameraView/branch/master/graph/badge.svg)](https://codecov.io/gh/natario1/CameraView)
+
@@ -409,6 +412,7 @@ Other APIs not mentioned above are provided, and are well documented and comment
|------|-----------|
|`isStarted()`|Returns true if `start()` was called succesfully. This does not mean that camera is open or showing preview.|
|`mapGesture(Gesture, GestureAction)`|Maps a certain gesture to a certain action. No-op if the action is not supported.|
+|`getGestureAction(Gesture)`|Returns the action currently mapped to the given gesture.|
|`clearGesture(Gesture)`|Clears any action mapped to the given gesture.|
|`getCameraOptions()`|If camera was started, returns non-null object with information about what is supported.|
|`getExtraProperties()`|If camera was started, returns non-null object with extra information about the camera sensor. Not very useful at the moment.|
diff --git a/cameraview/build.gradle b/cameraview/build.gradle
index f7f2d0de..7face5d3 100644
--- a/cameraview/build.gradle
+++ b/cameraview/build.gradle
@@ -6,6 +6,8 @@ apply plugin: 'com.jfrog.bintray'
version = '1.2.0'
group = 'com.otaliastudios'
+//region android dependencies
+
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
@@ -15,8 +17,14 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName project.version
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
+
buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -34,10 +42,18 @@ dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
+ androidTestCompile 'com.android.support.test:runner:1.0.1'
+ androidTestCompile 'com.android.support.test:rules:1.0.1'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
+
compile "com.android.support:exifinterface:$supportLibVersion"
compile "com.android.support:support-annotations:$supportLibVersion"
}
+//endregion
+
+//region bintray
install {
repositories.mavenInstaller {
@@ -104,6 +120,10 @@ bintray {
}
}
+//endregion
+
+//region javadoc and sources
+
// From official sample https://github.com/bintray/bintray-examples/blob/master/gradle-aar-example/build.gradle
task sourcesJar(type: Jar) {
classifier = 'sources'
@@ -134,6 +154,58 @@ artifacts {
archives sourcesJar
}
+//endregion
+
+//region code coverage
+
+// 1. running androidTests with connectedCheck will generate an .ec file
+// in build/outputs/code-coverage/connected, plus the XML result in
+// in build/reports/coverage/debug/report.xml .
+
+// 2. running unit tests with testDebugUnitTest will just generate the .exec file.
+// The JacocoReport task from the jacoco plugin can create the XML report out of it.
+
+// to have a unified report, we just pass both the .exec and the .ec file
+// to the jacoco task, so we get a unified XML report with total coverage.
+// Reference: https://medium.com/@rafael_toledo/setting-up-an-unified-coverage-report-in-android-with-jacoco-robolectric-and-espresso-ffe239aaf3fa
+
+apply plugin: 'jacoco'
+
+jacoco {
+ // Using a different version might get 0 coverage reports.
+ // No time to investigate now.
+ toolVersion = "0.7.6.201602180812"
+ reportsDir = file("$buildDir/reports/jacoco/merged/")
+}
+
+task coverageReport(type: JacocoReport) {
+ dependsOn "testDebugUnitTest"
+ dependsOn "connectedCheck"
+
+ def testData = "jacoco/testDebugUnitTest.exec"
+ def androidTestData = "outputs/code-coverage/connected/*coverage.ec"
+ executionData = fileTree(dir: "$buildDir", includes: [testData, androidTestData])
+
+ // Sources.
+ sourceDirectories = android.sourceSets.main.java.sourceFiles
+ // Add BuildConfig and R.
+ additionalSourceDirs = files([
+ "$buildDir/generated/source/buildConfig/debug",
+ "$buildDir/generated/source/r/debug"
+ ])
+
+ // Classes.
+ def debugDir = "$buildDir/intermediates/classes/debug"
+ def filter = ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*',
+ '**/BuildConfig.*', '**/Manifest*.*']
+ classDirectories = fileTree(dir: debugDir, excludes: filter);
+
+ reports.xml.enabled = true
+ reports.html.enabled = true
+}
+
+//endregion
+
// export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home
// To deploy ./gradlew bintrayUpload
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java
new file mode 100644
index 00000000..715a95b5
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java
@@ -0,0 +1,58 @@
+package com.otaliastudios.cameraview;
+
+
+import android.annotation.TargetApi;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraCharacteristics;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SizeF;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AspectRatioTest {
+
+ @Test
+ public void testConstructor() {
+ AspectRatio ratio = AspectRatio.of(50, 10);
+ assertEquals(ratio.getX(), 5f, 0);
+ assertEquals(ratio.getY(), 1f, 0);
+ assertEquals(ratio.toString(), "5:1");
+ }
+
+ @Test
+ public void testEquals() {
+ AspectRatio ratio = AspectRatio.of(50, 10);
+ AspectRatio ratio1 = AspectRatio.of(5, 1);
+ assertTrue(ratio.equals(ratio1));
+
+ Size size = new Size(500, 100);
+ assertTrue(ratio.matches(size));
+ }
+
+ @Test
+ public void testInverse() {
+ AspectRatio ratio = AspectRatio.of(50, 10);
+ AspectRatio inverse = ratio.inverse();
+ assertEquals(inverse.getX(), 1f, 0);
+ assertEquals(inverse.getY(), 5f, 0);
+ }
+
+ @Test
+ public void testParcelable() {
+ AspectRatio ratio = AspectRatio.of(50, 10);
+ Parcel parcel = Parcel.obtain();
+ ratio.writeToParcel(parcel, ratio.describeContents());
+
+ parcel.setDataPosition(0);
+ AspectRatio other = AspectRatio.CREATOR.createFromParcel(parcel);
+ assertEquals(ratio, other);
+ }
+
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
new file mode 100644
index 00000000..fbf68a7a
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
@@ -0,0 +1,132 @@
+package com.otaliastudios.cameraview;
+
+
+import android.hardware.Camera;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.InstrumentationTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CameraOptions1Test {
+
+ @Test
+ public void testEmpty() {
+ CameraOptions o = new CameraOptions(mock(Camera.Parameters.class));
+ assertTrue(o.getSupportedWhiteBalance().isEmpty());
+ assertTrue(o.getSupportedFlash().isEmpty());
+ assertTrue(o.getSupportedHdr().isEmpty());
+ assertFalse(o.isAutoFocusSupported());
+ assertFalse(o.isExposureCorrectionSupported());
+ assertFalse(o.isVideoSnapshotSupported());
+ assertFalse(o.isZoomSupported());
+ assertEquals(o.getExposureCorrectionMaxValue(), 0f, 0);
+ assertEquals(o.getExposureCorrectionMinValue(), 0f, 0);
+ }
+
+ @Test
+ public void testFacing() {
+ Set supported = new HashSet<>();
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
+ Camera.getCameraInfo(i, cameraInfo);
+ supported.add(cameraInfo.facing);
+ }
+
+ CameraOptions o = new CameraOptions(mock(Camera.Parameters.class));
+ Mapper m = new Mapper.Mapper1();
+ Set s = o.getSupportedFacing();
+ assertEquals(o.getSupportedFacing().size(), supported.size());
+ for (Facing facing : s) {
+ assertTrue(supported.contains(m.map(facing)));
+ }
+ }
+
+ @Test
+ public void testWhiteBalance() {
+ Camera.Parameters params = mock(Camera.Parameters.class);
+ when(params.getSupportedWhiteBalance()).thenReturn(Arrays.asList(
+ Camera.Parameters.WHITE_BALANCE_AUTO, // Supported
+ Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT, // Supported
+ Camera.Parameters.WHITE_BALANCE_SHADE // Not supported
+ ));
+
+ CameraOptions o = new CameraOptions(params);
+ assertEquals(o.getSupportedWhiteBalance().size(), 2);
+ assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.AUTO));
+ assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.CLOUDY));
+ assertTrue(o.supports(WhiteBalance.AUTO));
+ assertTrue(o.supports(WhiteBalance.CLOUDY));
+ }
+
+ @Test
+ public void testFlash() {
+ Camera.Parameters params = mock(Camera.Parameters.class);
+ when(params.getSupportedFlashModes()).thenReturn(Arrays.asList(
+ Camera.Parameters.FLASH_MODE_AUTO, // Supported
+ Camera.Parameters.FLASH_MODE_TORCH, // Supported
+ Camera.Parameters.FLASH_MODE_RED_EYE // Not supported
+ ));
+
+ CameraOptions o = new CameraOptions(params);
+ assertEquals(o.getSupportedFlash().size(), 2);
+ assertTrue(o.getSupportedFlash().contains(Flash.AUTO));
+ assertTrue(o.getSupportedFlash().contains(Flash.TORCH));
+ assertTrue(o.supports(Flash.AUTO));
+ assertTrue(o.supports(Flash.TORCH));
+ }
+
+ @Test
+ public void testHdr() {
+ Camera.Parameters params = mock(Camera.Parameters.class);
+ when(params.getSupportedSceneModes()).thenReturn(Arrays.asList(
+ Camera.Parameters.SCENE_MODE_AUTO, // Supported
+ Camera.Parameters.SCENE_MODE_HDR, // Supported
+ Camera.Parameters.SCENE_MODE_FIREWORKS // Not supported
+ ));
+
+ CameraOptions o = new CameraOptions(params);
+ assertEquals(o.getSupportedHdr().size(), 2);
+ assertTrue(o.getSupportedHdr().contains(Hdr.OFF));
+ assertTrue(o.getSupportedHdr().contains(Hdr.ON));
+ assertTrue(o.supports(Hdr.OFF));
+ assertTrue(o.supports(Hdr.ON));
+ }
+
+ @Test
+ public void testBooleanFlags() {
+ Camera.Parameters params = mock(Camera.Parameters.class);
+ when(params.isVideoSnapshotSupported()).thenReturn(true);
+ when(params.isZoomSupported()).thenReturn(true);
+ when(params.getSupportedFocusModes()).thenReturn(Arrays.asList(Camera.Parameters.FOCUS_MODE_AUTO));
+ CameraOptions o = new CameraOptions(params);
+ assertTrue(o.isVideoSnapshotSupported());
+ assertTrue(o.isZoomSupported());
+ assertTrue(o.isAutoFocusSupported());
+ }
+
+ @Test
+ public void testExposureCorrection() {
+ Camera.Parameters params = mock(Camera.Parameters.class);
+ when(params.getMaxExposureCompensation()).thenReturn(10);
+ when(params.getMinExposureCompensation()).thenReturn(-10);
+ when(params.getExposureCompensationStep()).thenReturn(0.5f);
+ CameraOptions o = new CameraOptions(params);
+ assertTrue(o.isExposureCorrectionSupported());
+ assertEquals(o.getExposureCorrectionMinValue(), -10f * 0.5f, 0f);
+ assertEquals(o.getExposureCorrectionMaxValue(), 10f * 0.5f, 0f);
+ }
+
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
new file mode 100644
index 00000000..4e8c94d5
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
@@ -0,0 +1,279 @@
+package com.otaliastudios.cameraview;
+
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.hardware.Camera;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+import static android.view.View.MeasureSpec.*;
+import static android.view.ViewGroup.LayoutParams.*;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CameraViewTest {
+
+ private CameraView cameraView;
+ private MockCameraController mockController;
+ private Preview mockPreview;
+
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ Context context = InstrumentationRegistry.getContext();
+ cameraView = new CameraView(context) {
+ @Override
+ protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
+ mockController = new MockCameraController(callbacks, preview);
+ return mockController;
+ }
+
+ @Override
+ protected Preview instantiatePreview(Context context, ViewGroup container) {
+ mockPreview = new MockPreview(context, container);
+ return mockPreview;
+ }
+ };
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ cameraView = null;
+ mockController = null;
+ mockPreview = null;
+ }
+
+ //region testDefaults
+
+ @Test
+ public void testNotStarted() {
+ assertFalse(cameraView.isStarted());
+ assertNull(cameraView.getCameraOptions());
+ assertNull(cameraView.getExtraProperties());
+ assertNull(cameraView.getPreviewSize());
+ assertNull(cameraView.getCaptureSize());
+ assertNull(cameraView.getSnapshotSize());
+ }
+
+ @Test
+ public void testDefaults() {
+ // CameraController
+ assertEquals(cameraView.getFlash(), Flash.DEFAULT);
+ assertEquals(cameraView.getFacing(), Facing.DEFAULT);
+ assertEquals(cameraView.getGrid(), Grid.DEFAULT);
+ assertEquals(cameraView.getWhiteBalance(), WhiteBalance.DEFAULT);
+ assertEquals(cameraView.getSessionType(), SessionType.DEFAULT);
+ assertEquals(cameraView.getHdr(), Hdr.DEFAULT);
+ assertEquals(cameraView.getVideoQuality(), VideoQuality.DEFAULT);
+
+ // Self managed
+ assertEquals(cameraView.getExposureCorrection(), 0f, 0f);
+ assertEquals(cameraView.getZoom(), 0f, 0f);
+ assertEquals(cameraView.getCropOutput(), CameraView.DEFAULT_CROP_OUTPUT);
+ assertEquals(cameraView.getJpegQuality(), CameraView.DEFAULT_JPEG_QUALITY);
+ assertEquals(cameraView.getGestureAction(Gesture.TAP), GestureAction.DEFAULT_TAP);
+ assertEquals(cameraView.getGestureAction(Gesture.LONG_TAP), GestureAction.DEFAULT_LONG_TAP);
+ assertEquals(cameraView.getGestureAction(Gesture.PINCH), GestureAction.DEFAULT_PINCH);
+ assertEquals(cameraView.getGestureAction(Gesture.SCROLL_HORIZONTAL), GestureAction.DEFAULT_SCROLL_HORIZONTAL);
+ assertEquals(cameraView.getGestureAction(Gesture.SCROLL_VERTICAL), GestureAction.DEFAULT_SCROLL_VERTICAL);
+ }
+
+ //endregion
+
+ //region testGesture
+
+ @Test
+ public void testGesture_mapAndClear() {
+ // Assignable
+ cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
+ assertEquals(cameraView.getGestureAction(Gesture.PINCH), GestureAction.ZOOM);
+
+ // Not assignable: This is like clearing
+ cameraView.mapGesture(Gesture.PINCH, GestureAction.CAPTURE);
+ assertEquals(cameraView.getGestureAction(Gesture.PINCH), GestureAction.NONE);
+
+ // Test clearing
+ cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
+ cameraView.clearGesture(Gesture.PINCH);
+ assertEquals(cameraView.getGestureAction(Gesture.PINCH), GestureAction.NONE);
+ }
+
+ @Test
+ public void testGesture_enablingDisablingLayouts() {
+ cameraView.clearGesture(Gesture.TAP);
+ cameraView.clearGesture(Gesture.LONG_TAP);
+ cameraView.clearGesture(Gesture.PINCH);
+ cameraView.clearGesture(Gesture.SCROLL_HORIZONTAL);
+ cameraView.clearGesture(Gesture.SCROLL_VERTICAL);
+
+ // PinchGestureLayout
+ cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
+ assertTrue(cameraView.mPinchGestureLayout.enabled());
+ cameraView.clearGesture(Gesture.PINCH);
+ assertFalse(cameraView.mPinchGestureLayout.enabled());
+
+ // TapGestureLayout
+ cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE);
+ assertTrue(cameraView.mTapGestureLayout.enabled());
+ cameraView.clearGesture(Gesture.TAP);
+ assertFalse(cameraView.mPinchGestureLayout.enabled());
+
+ // ScrollGestureLayout
+ cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.ZOOM);
+ assertTrue(cameraView.mScrollGestureLayout.enabled());
+ cameraView.clearGesture(Gesture.SCROLL_HORIZONTAL);
+ assertFalse(cameraView.mScrollGestureLayout.enabled());
+ }
+
+ //endregion
+
+ //region testMeasure
+
+ private void mockPreviewSize() {
+ Size size = new Size(900, 1600);
+ mockController.setMockPreviewSize(size);
+ }
+
+ @Test
+ public void testMeasure_matchParentBoth() {
+ mockPreviewSize();
+
+ // Respect parent/layout constraints on both dimensions.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ cameraView.measure(
+ makeMeasureSpec(500, EXACTLY),
+ makeMeasureSpec(500, EXACTLY));
+ assertEquals(cameraView.getMeasuredWidth(), 500);
+ assertEquals(cameraView.getMeasuredHeight(), 500);
+
+ // Even if the parent ViewGroup passes AT_MOST
+ cameraView.measure(
+ makeMeasureSpec(500, AT_MOST),
+ makeMeasureSpec(500, AT_MOST));
+ assertEquals(cameraView.getMeasuredWidth(), 500);
+ assertEquals(cameraView.getMeasuredHeight(), 500);
+
+ cameraView.measure(
+ makeMeasureSpec(500, EXACTLY),
+ makeMeasureSpec(500, AT_MOST));
+ assertEquals(cameraView.getMeasuredWidth(), 500);
+ assertEquals(cameraView.getMeasuredHeight(), 500);
+ }
+
+ @Test
+ public void testMeasure_wrapContentBoth() {
+ mockPreviewSize();
+
+ // Respect parent constraints, but fit aspect ratio.
+ // Fit into a 160x160 parent so we espect final width to be 90.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ cameraView.measure(
+ makeMeasureSpec(160, AT_MOST),
+ makeMeasureSpec(160, AT_MOST));
+ assertEquals(cameraView.getMeasuredWidth(), 90);
+ assertEquals(cameraView.getMeasuredHeight(), 160);
+ }
+
+ @Test
+ public void testMeasure_wrapContentSingle() {
+ mockPreviewSize();
+
+ // Respect MATCH_PARENT on height, change width to fit the aspect ratio.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, MATCH_PARENT));
+ cameraView.measure(
+ makeMeasureSpec(160, AT_MOST),
+ makeMeasureSpec(160, AT_MOST));
+ assertEquals(cameraView.getMeasuredWidth(), 90);
+ assertEquals(cameraView.getMeasuredHeight(), 160);
+
+ // Respect MATCH_PARENT on width. Enlarge height trying to fit aspect ratio as much as possible.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ cameraView.measure(
+ makeMeasureSpec(160, AT_MOST),
+ makeMeasureSpec(160, AT_MOST));
+ assertEquals(cameraView.getMeasuredWidth(), 160);
+ assertEquals(cameraView.getMeasuredHeight(), 160);
+ }
+
+ @Test
+ public void testMeasure_scrollableContainer() {
+ mockPreviewSize();
+
+ // Assume a vertical scroll view. It will pass UNSPECIFIED as height.
+ // We respect MATCH_PARENT on width (160), and enlarge height to match the aspect ratio.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ cameraView.measure(
+ makeMeasureSpec(160, AT_MOST),
+ makeMeasureSpec(0, UNSPECIFIED));
+ assertEquals(cameraView.getMeasuredWidth(), 160);
+ assertEquals(cameraView.getMeasuredHeight(), 160f * (16f / 9f), 1f); // Leave a margin
+
+ // Assume a view scrolling in both dimensions. It will pass UNSPECIFIED.
+ // In this case we must fit the exact preview dimension.
+ cameraView.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ cameraView.measure(
+ makeMeasureSpec(0, UNSPECIFIED),
+ makeMeasureSpec(0, UNSPECIFIED));
+ assertEquals(cameraView.getMeasuredWidth(), 900);
+ assertEquals(cameraView.getMeasuredHeight(), 1600);
+ }
+
+ //endregion
+
+ //region Zoom, ExposureCorrection
+
+ @Test
+ public void testZoom() {
+ cameraView.setZoom(0.5f);
+ assertEquals(cameraView.getZoom(), 0.5f, 0f);
+ cameraView.setZoom(-10f);
+ assertEquals(cameraView.getZoom(), 0f, 0f);
+ cameraView.setZoom(10f);
+ assertEquals(cameraView.getZoom(), 1f, 0f);
+ }
+
+ @Test
+ public void testExposureCorrection() {
+ // This needs a valid CameraOptions value.
+ CameraOptions o = mock(CameraOptions.class);
+ when(o.getExposureCorrectionMinValue()).thenReturn(-10f);
+ when(o.getExposureCorrectionMaxValue()).thenReturn(10f);
+ mockController.setMockCameraOptions(o);
+
+ cameraView.setExposureCorrection(5f);
+ assertEquals(cameraView.getExposureCorrection(), 5f, 0f);
+ cameraView.setExposureCorrection(-100f);
+ assertEquals(cameraView.getExposureCorrection(), -10f, 0f);
+ cameraView.setExposureCorrection(100f);
+ assertEquals(cameraView.getExposureCorrection(), 10f, 0f);
+ }
+
+ //endregion
+
+ // TODO: test setLocation
+
+ // TODO: test touch events
+
+ // TODO: test permissions and start() stop() isStarted()
+
+ // TODO: test CameraCallbacks
+
+}
diff --git a/cameraview/src/test/java/com/otaliastudios/cameraview/ExtraPropertiesTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/ExtraProperties1Test.java
similarity index 78%
rename from cameraview/src/test/java/com/otaliastudios/cameraview/ExtraPropertiesTest.java
rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/ExtraProperties1Test.java
index 166b182e..9a45eca7 100644
--- a/cameraview/src/test/java/com/otaliastudios/cameraview/ExtraPropertiesTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/ExtraProperties1Test.java
@@ -4,15 +4,20 @@ package com.otaliastudios.cameraview;
import android.annotation.TargetApi;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.util.SizeF;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
-public class ExtraPropertiesTest {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ExtraProperties1Test {
@Test
public void testConstructor1() {
diff --git a/cameraview/src/test/java/com/otaliastudios/cameraview/Mapper1Test.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/Mapper1Test.java
similarity index 94%
rename from cameraview/src/test/java/com/otaliastudios/cameraview/Mapper1Test.java
rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/Mapper1Test.java
index bbbaec78..71a93434 100644
--- a/cameraview/src/test/java/com/otaliastudios/cameraview/Mapper1Test.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/Mapper1Test.java
@@ -2,11 +2,17 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
+import org.junit.runner.RunWith;
import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
public class Mapper1Test {
private Mapper mapper = new Mapper.Mapper1();
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
new file mode 100644
index 00000000..113bc7ea
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
@@ -0,0 +1,124 @@
+package com.otaliastudios.cameraview;
+
+
+import android.graphics.PointF;
+import android.location.Location;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.File;
+
+public class MockCameraController extends CameraController {
+
+ MockCameraController(CameraView.CameraCallbacks callback, Preview preview) {
+ super(callback, preview);
+ }
+
+ void setMockCameraOptions(CameraOptions options) {
+ mOptions = options;
+ }
+
+ void setMockPreviewSize(Size size) {
+ mPreviewSize = size;
+ }
+
+ @Override
+ void onStart() {
+
+ }
+
+ @Override
+ void onStop() {
+
+ }
+
+ @Override
+ boolean setZoom(float zoom) {
+ return true;
+ }
+
+ @Override
+ boolean setExposureCorrection(float EVvalue) {
+ return true;
+ }
+
+ @Override
+ void setFacing(Facing facing) {
+ mFacing = facing;
+ }
+
+ @Override
+ void setFlash(Flash flash) {
+ mFlash = flash;
+ }
+
+ @Override
+ void setWhiteBalance(WhiteBalance whiteBalance) {
+ mWhiteBalance = whiteBalance;
+ }
+
+ @Override
+ void setVideoQuality(VideoQuality videoQuality) {
+ mVideoQuality = videoQuality;
+ }
+
+ @Override
+ void setSessionType(SessionType sessionType) {
+ mSessionType = sessionType;
+ }
+
+ @Override
+ void setHdr(Hdr hdr) {
+ mHdr = hdr;
+ }
+
+ @Override
+ void setLocation(Location location) {
+
+ }
+
+ @Override
+ boolean capturePicture() {
+ return true;
+ }
+
+ @Override
+ boolean captureSnapshot() {
+ return true;
+ }
+
+ @Override
+ boolean startVideo(@NonNull File file) {
+ return true;
+ }
+
+ @Override
+ boolean endVideo() {
+ return true;
+ }
+
+ @Override
+ boolean shouldFlipSizes() {
+ return false;
+ }
+
+ @Override
+ boolean isCameraOpened() {
+ return true;
+ }
+
+ @Override
+ boolean startAutoFocus(@Nullable Gesture gesture, PointF point) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceChanged() {
+
+ }
+
+ @Override
+ public void onSurfaceAvailable() {
+
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
new file mode 100644
index 00000000..eb2b1b8c
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
@@ -0,0 +1,34 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class MockPreview extends Preview {
+
+ public MockPreview(Context context, ViewGroup parent) {
+ super(context, parent);
+ }
+
+ @Override
+ Surface getSurface() {
+ return null;
+ }
+
+ @Override
+ View getView() {
+ return null;
+ }
+
+ @Override
+ Class getOutputClass() {
+ return null;
+ }
+
+ @Override
+ boolean isReady() {
+ return true;
+ }
+}
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
index 278bd790..83d11199 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
@@ -1,6 +1,7 @@
package com.otaliastudios.cameraview;
import android.Manifest;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
@@ -44,8 +45,8 @@ public class CameraView extends FrameLayout {
private final static String TAG = CameraView.class.getSimpleName();
public final static int PERMISSION_REQUEST_CODE = 16;
- private final static int DEFAULT_JPEG_QUALITY = 100;
- private final static boolean DEFAULT_CROP_OUTPUT = false;
+ final static int DEFAULT_JPEG_QUALITY = 100;
+ final static boolean DEFAULT_CROP_OUTPUT = false;
// Self managed parameters
private int mJpegQuality;
@@ -61,10 +62,10 @@ public class CameraView extends FrameLayout {
private Preview mPreviewImpl;
// Views
- private GridLinesLayout mGridLinesLayout;
- private PinchGestureLayout mPinchGestureLayout;
- private TapGestureLayout mTapGestureLayout;
- private ScrollGestureLayout mScrollGestureLayout;
+ GridLinesLayout mGridLinesLayout;
+ PinchGestureLayout mPinchGestureLayout;
+ TapGestureLayout mTapGestureLayout;
+ ScrollGestureLayout mScrollGestureLayout;
private boolean mIsStarted;
private boolean mKeepScreenOn;
@@ -98,6 +99,7 @@ public class CameraView extends FrameLayout {
WhiteBalance whiteBalance = WhiteBalance.fromValue(a.getInteger(R.styleable.CameraView_cameraWhiteBalance, WhiteBalance.DEFAULT.value()));
VideoQuality videoQuality = VideoQuality.fromValue(a.getInteger(R.styleable.CameraView_cameraVideoQuality, VideoQuality.DEFAULT.value()));
SessionType sessionType = SessionType.fromValue(a.getInteger(R.styleable.CameraView_cameraSessionType, SessionType.DEFAULT.value()));
+ Hdr hdr = Hdr.fromValue(a.getInteger(R.styleable.CameraView_cameraHdr, Hdr.DEFAULT.value()));
// Gestures
GestureAction tapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureTap, GestureAction.DEFAULT_TAP.value()));
@@ -109,8 +111,8 @@ public class CameraView extends FrameLayout {
// Components
mCameraCallbacks = new CameraCallbacks();
- mPreviewImpl = new TextureViewPreview(context, this);
- mCameraController = new Camera1(mCameraCallbacks, mPreviewImpl);
+ mPreviewImpl = instantiatePreview(context, this);
+ mCameraController = instantiateCameraController(mCameraCallbacks, mPreviewImpl);
mUiHandler = new Handler(Looper.getMainLooper());
mWorkerHandler = new WorkerHandler("CameraViewWorker");
@@ -137,6 +139,7 @@ public class CameraView extends FrameLayout {
setVideoQuality(videoQuality);
setWhiteBalance(whiteBalance);
setGrid(grid);
+ setHdr(hdr);
// Apply gestures
mapGesture(Gesture.TAP, tapGesture);
@@ -151,6 +154,17 @@ public class CameraView extends FrameLayout {
}
}
+
+ protected Preview instantiatePreview(Context context, ViewGroup container) {
+ return new TextureViewPreview(context, container);
+ }
+
+
+ protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
+ return new Camera1(callbacks, preview);
+ }
+
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -364,7 +378,18 @@ public class CameraView extends FrameLayout {
* @param gesture which gesture to clear
*/
public void clearGesture(@NonNull Gesture gesture) {
- mGestureMap.put(gesture, GestureAction.NONE);
+ mapGesture(gesture, GestureAction.NONE);
+ }
+
+
+ /**
+ * Returns the action currently mapped to the given gesture.
+ *
+ * @param gesture which gesture to inspect
+ * @return mapped action
+ */
+ public GestureAction getGestureAction(@NonNull Gesture gesture) {
+ return mGestureMap.get(gesture);
}
@@ -472,6 +497,7 @@ public class CameraView extends FrameLayout {
* Throws if session = audio and manifest did not add the microphone permissions.
* @return true if we can go on, false otherwise.
*/
+ @SuppressLint("NewApi")
private boolean checkPermissions(SessionType sessionType) {
checkPermissionsManifestOrThrow(sessionType);
boolean api23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
@@ -480,9 +506,7 @@ public class CameraView extends FrameLayout {
cameraCheck = PackageManager.PERMISSION_GRANTED;
audioCheck = PackageManager.PERMISSION_GRANTED;
} else {
- //noinspection all
cameraCheck = getContext().checkSelfPermission(Manifest.permission.CAMERA);
- //noinspection all
audioCheck = getContext().checkSelfPermission(Manifest.permission.RECORD_AUDIO);
}
switch (sessionType) {
@@ -922,12 +946,21 @@ public class CameraView extends FrameLayout {
*/
public void setJpegQuality(int jpegQuality) {
if (jpegQuality <= 0 || jpegQuality > 100) {
- throw new IllegalArgumentException("JPEG quality should be > 0 and <= 0");
+ throw new IllegalArgumentException("JPEG quality should be > 0 and <= 100");
}
mJpegQuality = jpegQuality;
}
+ /**
+ * Gets the JPEG compression quality for image outputs.
+ * @return a 0-100 integer
+ */
+ public int getJpegQuality() {
+ return mJpegQuality;
+ }
+
+
/**
* Whether we should crop the picture output to match CameraView aspect ratio.
* This is only relevant if CameraView dimensions were somehow constrained
@@ -942,6 +975,17 @@ public class CameraView extends FrameLayout {
}
+ /**
+ * Returns whether we should crop the picture output to match CameraView aspect ratio.
+ *
+ * @see #setCropOutput(boolean)
+ * @return whether we crop
+ */
+ public boolean getCropOutput() {
+ return mCropOutput;
+ }
+
+
/**
* Sets a {@link CameraListener} instance to be notified of all
* interesting events that will happen during the camera lifecycle.
@@ -1158,7 +1202,7 @@ public class CameraView extends FrameLayout {
private MediaActionSound mSound;
private final boolean mUseSounds = Build.VERSION.SDK_INT >= 16;
- @SuppressWarnings("all")
+ @SuppressLint("NewApi")
private void sound(int soundType) {
if (mUseSounds) {
if (mSound == null) mSound = new MediaActionSound();
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
index ad7cc975..5553b2da 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
@@ -29,6 +29,10 @@ abstract class GestureLayout extends FrameLayout {
mEnabled = enable;
}
+ public boolean enabled() {
+ return mEnabled;
+ }
+
public abstract boolean onTouchEvent(MotionEvent event);
public final Gesture getGestureType() {