diff --git a/.travis.yml b/.travis.yml
index 410e1a0f..c7c60469 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,10 +35,10 @@ before_script:
- adb shell input keyevent 82 &
script:
- - ./gradlew clean testDebugUnitTest connectedCheck coverageReport
+ - ./gradlew clean testDebugUnitTest connectedCheck mergedCoverageReport
after_success:
- - bash <(curl -s https://codecov.io/bash)
+ - bash <(curl -s https://codecov.io/bash) -s "*/build/reports/mergedCoverageReport/"
cache:
directories:
diff --git a/cameraview/build.gradle b/cameraview/build.gradle
index fd3a2c94..987db39e 100644
--- a/cameraview/build.gradle
+++ b/cameraview/build.gradle
@@ -46,6 +46,7 @@ dependencies {
androidTestCompile 'com.android.support.test:rules:1.0.1'
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
+ androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
compile "com.android.support:exifinterface:$supportLibVersion"
compile "com.android.support:support-annotations:$supportLibVersion"
@@ -175,14 +176,15 @@ artifacts {
apply plugin: 'jacoco'
+def reportsDirectory = "$buildDir/reports/"
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/")
+ reportsDir = file(reportsDirectory)
}
-task coverageReport(type: JacocoReport) {
+task mergedCoverageReport(type: JacocoReport) {
dependsOn "testDebugUnitTest"
dependsOn "connectedCheck"
@@ -206,6 +208,7 @@ task coverageReport(type: JacocoReport) {
reports.xml.enabled = true
reports.html.enabled = true
+ reports.xml.destination = "$reportsDirectory/mergedCoverageReport/report.xml"
}
//endregion
diff --git a/cameraview/src/androidTest/AndroidManifest.xml b/cameraview/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000..1795db1e
--- /dev/null
+++ b/cameraview/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
new file mode 100644
index 00000000..7f505479
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
@@ -0,0 +1,41 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.rule.ActivityTestRule;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+
+public class BaseTest {
+
+ public static void ui(Runnable runnable) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+ }
+
+ public static void uiAsync(Runnable runnable) {
+ new Handler(Looper.getMainLooper()).post(runnable);
+ }
+
+ public static Context context() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ public static void uiRequestLayout(final View view) {
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ view.requestLayout();
+ }
+ });
+ }
+
+ public static void waitUi() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java
new file mode 100644
index 00000000..92115c96
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java
@@ -0,0 +1,60 @@
+package com.otaliastudios.cameraview;
+
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.internal.runner.InstrumentationConnection;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CameraUtilsTest {
+
+ @Test
+ public void testHasCameras() {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.hasSystemFeature(anyString())).thenReturn(true);
+ assertTrue(CameraUtils.hasCameras(context));
+
+ when(pm.hasSystemFeature(anyString())).thenReturn(false);
+ assertFalse(CameraUtils.hasCameras(context));
+ }
+
+ @Test
+ public void testDecodeBitmap() {
+ int w = 100, h = 200, color = Color.WHITE;
+ Bitmap source = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ source.setPixel(0, 0, color);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ // Using lossy JPG we can't have strict comparison of values after compression.
+ source.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ // No orientation.
+ Bitmap other = CameraUtils.decodeBitmap(os.toByteArray());
+ 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));
+
+ // TODO: improve when we add EXIF writing to byte arrays
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
index 4e8c94d5..9e2f645d 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
@@ -4,10 +4,12 @@ package com.otaliastudios.cameraview;
import android.app.Instrumentation;
import android.content.Context;
import android.hardware.Camera;
+import android.location.Location;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -25,15 +27,17 @@ import static android.view.ViewGroup.LayoutParams.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
-public class CameraViewTest {
+public class CameraViewTest extends BaseTest {
private CameraView cameraView;
private MockCameraController mockController;
private Preview mockPreview;
+ private boolean hasPermissions;
+
@Before
public void setUp() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ ui(new Runnable() {
@Override
public void run() {
Context context = InstrumentationRegistry.getContext();
@@ -49,6 +53,11 @@ public class CameraViewTest {
mockPreview = new MockPreview(context, container);
return mockPreview;
}
+
+ @Override
+ protected boolean checkPermissions(SessionType sessionType) {
+ return hasPermissions;
+ }
};
}
});
@@ -59,12 +68,13 @@ public class CameraViewTest {
cameraView = null;
mockController = null;
mockPreview = null;
+ hasPermissions = false;
}
//region testDefaults
@Test
- public void testNotStarted() {
+ public void testNullBeforeStart() {
assertFalse(cameraView.isStarted());
assertNull(cameraView.getCameraOptions());
assertNull(cameraView.getExtraProperties());
@@ -96,6 +106,23 @@ public class CameraViewTest {
assertEquals(cameraView.getGestureAction(Gesture.SCROLL_VERTICAL), GestureAction.DEFAULT_SCROLL_VERTICAL);
}
+ @Test
+ public void testStartWithPermissions() {
+ hasPermissions = true;
+ cameraView.start();
+ assertTrue(cameraView.isStarted());
+
+ cameraView.stop();
+ assertFalse(cameraView.isStarted());
+ }
+
+ @Test
+ public void testStartWithoutPermissions() {
+ hasPermissions = false;
+ cameraView.start();
+ assertFalse(cameraView.isStarted());
+ }
+
//endregion
//region testGesture
@@ -145,6 +172,94 @@ public class CameraViewTest {
//endregion
+ //region testGestureAction
+
+ @Test
+ public void testGestureAction_capture() {
+ MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) {
+ public boolean onTouchEvent(MotionEvent event) { return true; }
+
+ };
+ cameraView.mTapGestureLayout.setGestureType(Gesture.TAP);
+ }
+ });
+ cameraView.mapGesture(Gesture.TAP, GestureAction.CAPTURE);
+ cameraView.dispatchTouchEvent(event);
+ assertTrue(mockController.mPictureCaptured);
+ }
+
+ @Test
+ public void testGestureAction_focus() {
+ MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ cameraView.mTapGestureLayout = new TapGestureLayout(cameraView.getContext()) {
+ public boolean onTouchEvent(MotionEvent event) { return true; }
+ };
+ cameraView.mTapGestureLayout.setGestureType(Gesture.TAP);
+ }
+ });
+ mockController.mFocusStarted = false;
+ cameraView.mapGesture(Gesture.TAP, GestureAction.FOCUS);
+ cameraView.dispatchTouchEvent(event);
+ assertTrue(mockController.mFocusStarted);
+
+ // Try with FOCUS_WITH_MARKER
+ mockController.mFocusStarted = false;
+ cameraView.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER);
+ cameraView.dispatchTouchEvent(event);
+ assertTrue(mockController.mFocusStarted);
+ }
+
+ @Test
+ public void testGestureAction_zoom() {
+ MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ cameraView.mPinchGestureLayout = new PinchGestureLayout(cameraView.getContext()) {
+ public boolean onTouchEvent(MotionEvent event) { return true; }
+ };
+ cameraView.mPinchGestureLayout.setGestureType(Gesture.PINCH);
+ }
+ });
+ mockController.mZoomChanged = false;
+ cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
+ cameraView.dispatchTouchEvent(event);
+ assertTrue(mockController.mZoomChanged);
+ }
+
+ @Test
+ public void testGestureAction_exposureCorrection() {
+ // This needs a valid CameraOptions value.
+ CameraOptions o = mock(CameraOptions.class);
+ when(o.getExposureCorrectionMinValue()).thenReturn(-10f);
+ when(o.getExposureCorrectionMaxValue()).thenReturn(10f);
+ mockController.setMockCameraOptions(o);
+
+ MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ cameraView.mScrollGestureLayout = new ScrollGestureLayout(cameraView.getContext()) {
+ public boolean onTouchEvent(MotionEvent event) { return true; }
+ };
+ cameraView.mScrollGestureLayout.setGestureType(Gesture.SCROLL_HORIZONTAL);
+ }
+ });
+ mockController.mExposureCorrectionChanged = false;
+ cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION);
+ cameraView.dispatchTouchEvent(event);
+ assertTrue(mockController.mExposureCorrectionChanged);
+ }
+
+ //endregion
+
//region testMeasure
private void mockPreviewSize() {
@@ -268,11 +383,34 @@ public class CameraViewTest {
//endregion
- // TODO: test setLocation
+ //region testLocation
- // TODO: test touch events
+ @Test
+ public void testSetLocation() {
+ cameraView.setLocation(50d, -50d);
+ assertEquals(50d, mockController.mLocation.getLatitude(), 0);
+ assertEquals(-50d, mockController.mLocation.getLongitude(), 0);
+ assertEquals(0, mockController.mLocation.getAltitude(), 0);
+ assertEquals("Unknown", mockController.mLocation.getProvider());
+ assertEquals(System.currentTimeMillis(), mockController.mLocation.getTime(), 1000f);
+
+ Location source = new Location("Provider");
+ source.setTime(5000);
+ source.setLatitude(10d);
+ source.setLongitude(-10d);
+ source.setAltitude(50d);
+ cameraView.setLocation(source);
+ Location other = mockController.mLocation;
+ assertEquals(10d, other.getLatitude(), 0d);
+ assertEquals(-10d, other.getLongitude(), 0d);
+ assertEquals(50d, other.getAltitude(), 0d);
+ assertEquals("Provider", other.getProvider());
+ assertEquals(5000, other.getTime());
+ }
+
+ //endregion
- // TODO: test permissions and start() stop() isStarted()
+ // TODO: test permissions
// TODO: test CameraCallbacks
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CropHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CropHelperTest.java
new file mode 100644
index 00000000..993f3bd9
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CropHelperTest.java
@@ -0,0 +1,92 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CropHelperTest {
+
+ @Test
+ public void testCropFromYuv() {
+ testCropFromYuv(1600, 1600, AspectRatio.of(16, 9));
+ testCropFromYuv(1600, 1600, AspectRatio.of(9, 16));
+ }
+
+ private void testCropFromYuv(final int w, final int h, final AspectRatio target) {
+ final boolean wider = target.toFloat() > ((float) w / (float) h);
+
+ // Not sure how to test YuvImages...
+ YuvImage i = mock(YuvImage.class);
+ when(i.getWidth()).thenReturn(w);
+ when(i.getHeight()).thenReturn(h);
+ when(i.compressToJpeg(any(Rect.class), anyInt(), any(OutputStream.class))).thenAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock iom) throws Throwable {
+ Object[] args = iom.getArguments();
+ Rect rect = (Rect) args[0];
+
+ // Assert.
+ AspectRatio ratio = AspectRatio.of(rect.width(), rect.height());
+ assertEquals(target, ratio);
+ if (wider) { // width must match.
+ assertEquals(rect.width(), w);
+ } else {
+ assertEquals(rect.height(), h);
+ }
+ return true;
+ }
+ });
+ CropHelper.cropToJpeg(i, target, 100);
+ }
+
+ @Test
+ public void testCropFromJpeg() {
+ testCropFromJpeg(1600, 1600, AspectRatio.of(16, 9));
+ testCropFromJpeg(1600, 1600, AspectRatio.of(9, 16));
+ }
+
+ private void testCropFromJpeg(int w, int h, AspectRatio target) {
+ final boolean wider = target.toFloat() > ((float) w / (float) h);
+
+ Bitmap source = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ source.compress(Bitmap.CompressFormat.JPEG, 100, os);
+ byte[] b = CropHelper.cropToJpeg(os.toByteArray(), target, 100);
+ Bitmap result = BitmapFactory.decodeByteArray(b, 0, b.length);
+
+ // Assert.
+ AspectRatio ratio = AspectRatio.of(result.getWidth(), result.getHeight());
+ assertEquals(target, ratio);
+ if (wider) { // width must match.
+ assertEquals(result.getWidth(), w);
+ } else {
+ assertEquals(result.getHeight(), h);
+ }
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GestureLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GestureLayoutTest.java
new file mode 100644
index 00000000..e6c2a5cf
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GestureLayoutTest.java
@@ -0,0 +1,62 @@
+package com.otaliastudios.cameraview;
+
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.Root;
+import android.support.test.espresso.ViewAssertion;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.espresso.assertion.ViewAssertions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.support.test.rule.ActivityTestRule;
+import android.view.MotionEvent;
+import android.view.View;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Rule;
+
+import static android.support.test.espresso.Espresso.onView;
+import static org.hamcrest.Matchers.any;
+
+@TargetApi(17)
+public abstract class GestureLayoutTest extends BaseTest {
+
+ protected abstract T create(Context context);
+
+ @Rule
+ public ActivityTestRule rule = new ActivityTestRule<>(TestActivity.class);
+
+ protected T layout;
+ protected Task touch;
+
+ @Before
+ public void setUp() {
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ TestActivity a = rule.getActivity();
+ layout = create(a);
+ layout.enable(true);
+ a.inflate(layout);
+
+ touch = new Task<>();
+ layout.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ boolean found = layout.onTouchEvent(motionEvent);
+ if (found) touch.end(layout.getGestureType());
+ return true;
+ }
+ });
+ }
+ });
+ }
+
+ protected final ViewInteraction onLayout() {
+ return onView(Matchers.is(layout)).inRoot(any(Root.class));
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GridLinesLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GridLinesLayoutTest.java
new file mode 100644
index 00000000..8869ce10
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/GridLinesLayoutTest.java
@@ -0,0 +1,78 @@
+package com.otaliastudios.cameraview;
+
+
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.Mockito.*;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class GridLinesLayoutTest extends BaseTest {
+
+ @Rule
+ public ActivityTestRule rule = new ActivityTestRule<>(TestActivity.class);
+
+ private GridLinesLayout layout;
+
+ @Before
+ public void setUp() {
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ TestActivity a = rule.getActivity();
+ layout = new GridLinesLayout(a);
+ layout.setGridMode(Grid.OFF);
+ a.getContentView().addView(layout);
+ layout.vert = mock(ColorDrawable.class);
+ layout.horiz = mock(ColorDrawable.class);
+ }
+ });
+ }
+
+ @Test
+ public void testOff() {
+ layout.drawTask.listen();
+ layout.setGridMode(Grid.OFF);
+ layout.drawTask.await();
+ verify(layout.vert, never()).draw(any(Canvas.class));
+ verify(layout.horiz, never()).draw(any(Canvas.class));
+ }
+
+ @Test
+ public void test3x3() {
+ layout.drawTask.listen();
+ layout.setGridMode(Grid.DRAW_3X3);
+ layout.drawTask.await();
+ verify(layout.vert, times(2)).draw(any(Canvas.class));
+ verify(layout.horiz, times(2)).draw(any(Canvas.class));
+ }
+
+ @Test
+ public void testPhi() {
+ layout.drawTask.listen();
+ layout.setGridMode(Grid.DRAW_PHI);
+ layout.drawTask.await();
+ verify(layout.vert, times(2)).draw(any(Canvas.class));
+ verify(layout.horiz, times(2)).draw(any(Canvas.class));
+ }
+
+ @Test
+ public void test4x4() {
+ layout.drawTask.listen();
+ layout.setGridMode(Grid.DRAW_4X4);
+ layout.drawTask.await();
+ verify(layout.vert, times(3)).draw(any(Canvas.class));
+ verify(layout.horiz, times(3)).draw(any(Canvas.class));
+ }
+
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
index 113bc7ea..ce150fd2 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
@@ -10,6 +10,12 @@ import java.io.File;
public class MockCameraController extends CameraController {
+ Location mLocation;
+ boolean mPictureCaptured;
+ boolean mFocusStarted;
+ boolean mZoomChanged;
+ boolean mExposureCorrectionChanged;
+
MockCameraController(CameraView.CameraCallbacks callback, Preview preview) {
super(callback, preview);
}
@@ -24,21 +30,21 @@ public class MockCameraController extends CameraController {
@Override
void onStart() {
-
}
@Override
void onStop() {
-
}
@Override
boolean setZoom(float zoom) {
+ mZoomChanged = true;
return true;
}
@Override
boolean setExposureCorrection(float EVvalue) {
+ mExposureCorrectionChanged = true;
return true;
}
@@ -74,11 +80,12 @@ public class MockCameraController extends CameraController {
@Override
void setLocation(Location location) {
-
+ mLocation = location;
}
@Override
boolean capturePicture() {
+ mPictureCaptured = true;
return true;
}
@@ -109,6 +116,7 @@ public class MockCameraController extends CameraController {
@Override
boolean startAutoFocus(@Nullable Gesture gesture, PointF point) {
+ mFocusStarted = true;
return true;
}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
index eb2b1b8c..e011c617 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
@@ -2,33 +2,36 @@ package com.otaliastudios.cameraview;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-public class MockPreview extends Preview {
+public class MockPreview extends Preview {
- public MockPreview(Context context, ViewGroup parent) {
- super(context, parent);
+ MockPreview(Context context, ViewGroup parent) {
+ super(context, parent, null);
}
+ @NonNull
@Override
- Surface getSurface() {
- return null;
+ protected View onCreateView(Context context, ViewGroup parent) {
+ return new View(context);
}
@Override
- View getView() {
+ Surface getSurface() {
return null;
}
@Override
- Class getOutputClass() {
+ Class getOutputClass() {
return null;
}
@Override
- boolean isReady() {
- return true;
+ Void getOutput() {
+ return null;
}
+
}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/OrientationHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/OrientationHelperTest.java
new file mode 100644
index 00000000..19a6aa83
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/OrientationHelperTest.java
@@ -0,0 +1,97 @@
+package com.otaliastudios.cameraview;
+
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.OrientationEventListener;
+
+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.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class OrientationHelperTest extends BaseTest {
+
+ private OrientationHelper helper;
+ private OrientationHelper.Callbacks callbacks;
+
+ @Before
+ public void setUp() {
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ callbacks = mock(OrientationHelper.Callbacks.class);
+ helper = new OrientationHelper(context(), callbacks);
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ callbacks = null;
+ helper = null;
+ }
+
+ @Test
+ public void testEnable() {
+ assertNotNull(helper.mListener);
+ assertNull(helper.mDisplay);
+
+ helper.enable(context());
+ assertNotNull(helper.mListener);
+ assertNotNull(helper.mDisplay);
+
+ // Ensure nothing bad if called twice.
+ helper.enable(context());
+ assertNotNull(helper.mListener);
+ assertNotNull(helper.mDisplay);
+
+ helper.disable();
+ assertNotNull(helper.mListener);
+ assertNull(helper.mDisplay);
+
+ verify(callbacks, atLeastOnce()).onDisplayOffsetChanged(anyInt());
+ }
+
+ @Test
+ public void testRotation() {
+ helper.enable(context());
+
+ reset(callbacks); // Reset counts.
+ helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
+ assertEquals(helper.mLastOrientation, 0);
+ helper.mListener.onOrientationChanged(10);
+ assertEquals(helper.mLastOrientation, 0);
+ helper.mListener.onOrientationChanged(-10);
+ assertEquals(helper.mLastOrientation, 0);
+ helper.mListener.onOrientationChanged(44);
+ assertEquals(helper.mLastOrientation, 0);
+ helper.mListener.onOrientationChanged(360);
+ assertEquals(helper.mLastOrientation, 0);
+
+ // Callback called just once.
+ verify(callbacks, times(1)).onDeviceOrientationChanged(0);
+
+ helper.mListener.onOrientationChanged(90);
+ helper.mListener.onOrientationChanged(91);
+ assertEquals(helper.mLastOrientation, 90);
+ verify(callbacks, times(1)).onDeviceOrientationChanged(90);
+
+ helper.mListener.onOrientationChanged(180);
+ assertEquals(helper.mLastOrientation, 180);
+ verify(callbacks, times(1)).onDeviceOrientationChanged(180);
+
+ helper.mListener.onOrientationChanged(270);
+ assertEquals(helper.mLastOrientation, 270);
+ verify(callbacks, times(1)).onDeviceOrientationChanged(270);
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PinchGestureLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PinchGestureLayoutTest.java
new file mode 100644
index 00000000..d1af6e1d
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PinchGestureLayoutTest.java
@@ -0,0 +1,86 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralLocation;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.assertion.ViewAssertions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PinchGestureLayoutTest extends GestureLayoutTest {
+
+ @Override
+ protected PinchGestureLayout create(Context context) {
+ return new PinchGestureLayout(context);
+ }
+
+ @Test
+ public void testDefaults() {
+ assertEquals(layout.getGestureType(), Gesture.PINCH);
+ assertEquals(layout.getPoints().length, 2);
+ assertEquals(layout.getPoints()[0].x, 0, 0);
+ assertEquals(layout.getPoints()[0].y, 0, 0);
+ assertEquals(layout.getPoints()[1].x, 0, 0);
+ assertEquals(layout.getPoints()[1].y, 0, 0);
+ }
+
+ // TODO: test pinch open
+ // TODO: test pinch close
+ // TODO: test pinch disabled
+
+ // Possible approach: mimic pinch gesture and let espresso test.
+ // Too lazy to do this now, but it's possible.
+ // https://stackoverflow.com/questions/11523423/how-to-generate-zoom-pinch-gesture-for-testing-for-android
+
+ public abstract class PinchViewAction implements ViewAction {
+ }
+
+ private void testPinch(ViewAction action, boolean increasing) {
+ touch.listen();
+ touch.start();
+ onLayout().perform(action);
+ Gesture found = touch.await(10000, TimeUnit.MILLISECONDS);
+ assertNotNull(found);
+
+ // How will this move our parameter?
+ float curr = 0.5f, min = 0f, max = 1f;
+ float newValue = layout.scaleValue(curr, min, max);
+ if (increasing) {
+ assertTrue(newValue > curr);
+ assertTrue(newValue <= max);
+ } else {
+ assertTrue(newValue < curr);
+ assertTrue(newValue >= min);
+ }
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
new file mode 100644
index 00000000..8fbad240
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
@@ -0,0 +1,157 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.rule.ActivityTestRule;
+import android.view.ViewGroup;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+public abstract class PreviewTest extends BaseTest {
+
+ protected abstract Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback);
+
+ @Rule
+ public ActivityTestRule rule = new ActivityTestRule<>(TestActivity.class);
+
+ protected Preview preview;
+ protected Size surfaceSize;
+ private Preview.SurfaceCallback callback;
+ private Task availability;
+
+ @Before
+ public void setUp() {
+ availability = new Task<>();
+ availability.listen();
+
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ TestActivity a = rule.getActivity();
+ surfaceSize = a.getContentSize();
+
+ callback = mock(Preview.SurfaceCallback.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ if (availability != null) availability.end(true);
+ return null;
+ }
+ }).when(callback).onSurfaceAvailable();
+ preview = createPreview(a, a.getContentView(), callback);
+ }
+ });
+ }
+
+ // Wait for surface to be available.
+ protected void ensureAvailable() {
+ assertNotNull(availability.await(2, TimeUnit.SECONDS));
+ }
+
+ // Trigger a destroy.
+ protected void ensureDestroyed() {
+ ui(new Runnable() {
+ @Override
+ public void run() {
+ rule.getActivity().getContentView().removeView(preview.getView());
+ }
+ });
+ waitUi();
+ }
+
+ @After
+ public void tearDown() {
+ preview = null;
+ callback = null;
+ surfaceSize = null;
+ availability = null;
+ }
+
+ @Test
+ public void testDefaults() {
+ ensureAvailable();
+ assertTrue(preview.isReady());
+ assertNotNull(preview.getView());
+ assertNotNull(preview.getOutputClass());
+ }
+
+ @Test
+ public void testDesiredSize() {
+ preview.setDesiredSize(160, 90);
+ assertEquals(160, preview.getDesiredSize().getWidth());
+ assertEquals(90, preview.getDesiredSize().getHeight());
+ }
+
+ @Test
+ public void testSurfaceAvailable() {
+ ensureAvailable();
+ verify(callback, times(1)).onSurfaceAvailable();
+ assertEquals(surfaceSize.getWidth(), preview.getSurfaceSize().getWidth());
+ assertEquals(surfaceSize.getHeight(), preview.getSurfaceSize().getHeight());
+ }
+
+ @Test
+ public void testSurfaceDestroyed() {
+ ensureAvailable();
+ ensureDestroyed();
+ assertEquals(0, preview.getSurfaceSize().getWidth());
+ assertEquals(0, preview.getSurfaceSize().getHeight());
+ }
+
+ @Test
+ public void testCropCenter() throws Exception {
+ ensureAvailable();
+
+ // This is given by the activity, it's the fixed size.
+ float view = getViewAspectRatio();
+
+ // If we apply a desired size with same aspect ratio, there should be no crop.
+ setDesiredAspectRatio(view);
+ assertFalse(preview.isCropping());
+
+ // If we apply a different aspect ratio, there should be cropping.
+ float desired = view * 1.2f;
+ setDesiredAspectRatio(desired);
+ assertTrue(preview.isCropping());
+
+ // Since desired is 'desired', let's fake a new view size that is consistent with it.
+ // Ensure crop is not happening anymore.
+ preview.mCropTask.listen();
+ preview.onSurfaceSizeChanged((int) (50f * desired), 50); // Wait...
+ preview.mCropTask.await();
+ assertEquals(desired, getViewAspectRatioWithScale(), 0.01f);
+ assertFalse(preview.isCropping());
+ }
+
+ private void setDesiredAspectRatio(float desiredAspectRatio) {
+ preview.mCropTask.listen();
+ preview.setDesiredSize((int) (10f * desiredAspectRatio), 10); // Wait...
+ preview.mCropTask.await();
+ assertEquals(desiredAspectRatio, getViewAspectRatioWithScale(), 0.01f);
+
+ }
+
+ private float getViewAspectRatio() {
+ Size size = preview.getSurfaceSize();
+ return AspectRatio.of(size.getWidth(), size.getHeight()).toFloat();
+ }
+
+ private float getViewAspectRatioWithScale() {
+ Size size = preview.getSurfaceSize();
+ int newWidth = (int) (((float) size.getWidth()) * preview.getView().getScaleX());
+ int newHeight = (int) (((float) size.getHeight()) * preview.getView().getScaleY());
+ return AspectRatio.of(newWidth, newHeight).toFloat();
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/ScrollGestureLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/ScrollGestureLayoutTest.java
new file mode 100644
index 00000000..ad5e8433
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/ScrollGestureLayoutTest.java
@@ -0,0 +1,97 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.assertion.ViewAssertions;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.swipeDown;
+import static android.support.test.espresso.action.ViewActions.swipeLeft;
+import static android.support.test.espresso.action.ViewActions.swipeRight;
+import static android.support.test.espresso.action.ViewActions.swipeUp;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static junit.framework.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ScrollGestureLayoutTest extends GestureLayoutTest {
+
+ @Override
+ protected ScrollGestureLayout create(Context context) {
+ return new ScrollGestureLayout(context);
+ }
+
+ @Test
+ public void testDefaults() {
+ assertNull(layout.getGestureType());
+ assertEquals(layout.getPoints().length, 2);
+ assertEquals(layout.getPoints()[0].x, 0, 0);
+ assertEquals(layout.getPoints()[0].y, 0, 0);
+ assertEquals(layout.getPoints()[1].x, 0, 0);
+ assertEquals(layout.getPoints()[1].y, 0, 0);
+ }
+
+ @Test
+ public void testScrollDisabled() {
+ layout.enable(false);
+ touch.listen();
+ touch.start();
+ onLayout().perform(swipeUp());
+ Gesture found = touch.await(500, TimeUnit.MILLISECONDS);
+ assertNull(found);
+ }
+
+ private void testScroll(ViewAction scroll, Gesture expected, boolean increasing) {
+ touch.listen();
+ touch.start();
+ onLayout().perform(scroll);
+ Gesture found = touch.await(500, TimeUnit.MILLISECONDS);
+ assertEquals(found, expected);
+
+ // How will this move our parameter?
+ float curr = 0.5f, min = 0f, max = 1f;
+ float newValue = layout.scaleValue(curr, min, max);
+ if (increasing) {
+ assertTrue(newValue > curr);
+ assertTrue(newValue <= max);
+ } else {
+ assertTrue(newValue < curr);
+ assertTrue(newValue >= min);
+ }
+ }
+
+ @Test
+ public void testScrollLeft() {
+ testScroll(swipeLeft(), Gesture.SCROLL_HORIZONTAL, false);
+ }
+
+ @Test
+ public void testScrollRight() {
+ testScroll(swipeRight(), Gesture.SCROLL_HORIZONTAL, true);
+ }
+
+ @Test
+ public void testScrollUp() {
+ testScroll(swipeUp(), Gesture.SCROLL_VERTICAL, true);
+ }
+
+ @Test
+ public void testScrollDown() {
+ testScroll(swipeDown(), Gesture.SCROLL_VERTICAL, false);
+ }
+
+
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceViewPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceViewPreviewTest.java
new file mode 100644
index 00000000..afa18eb2
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceViewPreviewTest.java
@@ -0,0 +1,19 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.ViewGroup;
+
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SurfaceViewPreviewTest extends PreviewTest {
+
+ @Override
+ protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) {
+ return new SurfaceViewPreview(context, parent, callback);
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TapGestureLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TapGestureLayoutTest.java
new file mode 100644
index 00000000..cf976b5f
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TapGestureLayoutTest.java
@@ -0,0 +1,81 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.GeneralLocation;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Tap;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static android.support.test.espresso.Espresso.*;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static android.support.test.espresso.action.ViewActions.*;
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TapGestureLayoutTest extends GestureLayoutTest {
+
+ @Override
+ protected TapGestureLayout create(Context context) {
+ return new TapGestureLayout(context);
+ }
+
+ @Test
+ public void testDefaults() {
+ assertNull(layout.getGestureType());
+ assertEquals(layout.getPoints().length, 1);
+ assertEquals(layout.getPoints()[0].x, 0, 0);
+ assertEquals(layout.getPoints()[0].y, 0, 0);
+ }
+
+ @Test
+ public void testTap() {
+ touch.listen();
+ touch.start();
+ GeneralClickAction a = new GeneralClickAction(
+ Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER,
+ InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY);
+ onLayout().perform(a);
+ Gesture found = touch.await(500, TimeUnit.MILLISECONDS);
+
+ assertEquals(found, Gesture.TAP);
+ Size size = rule.getActivity().getContentSize();
+ assertEquals(layout.getPoints()[0].x, (size.getWidth() / 2f), 1f);
+ assertEquals(layout.getPoints()[0].y, (size.getHeight() / 2f), 1f);
+ }
+
+ @Test
+ public void testTapWhileDisabled() {
+ layout.enable(false);
+ touch.listen();
+ touch.start();
+ onLayout().perform(click());
+ Gesture found = touch.await(500, TimeUnit.MILLISECONDS);
+ assertNull(found);
+ }
+
+ @Test
+ public void testLongTap() {
+ touch.listen();
+ touch.start();
+ GeneralClickAction a = new GeneralClickAction(
+ Tap.LONG, GeneralLocation.CENTER, Press.FINGER,
+ InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY);
+ onLayout().perform(a);
+ Gesture found = touch.await(500, TimeUnit.MILLISECONDS);
+ assertEquals(found, Gesture.LONG_TAP);
+ Size size = rule.getActivity().getContentSize();
+ assertEquals(layout.getPoints()[0].x, (size.getWidth() / 2f), 1f);
+ assertEquals(layout.getPoints()[0].y, (size.getHeight() / 2f), 1f);
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TestActivity.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TestActivity.java
new file mode 100644
index 00000000..92c9c69b
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TestActivity.java
@@ -0,0 +1,75 @@
+package com.otaliastudios.cameraview;
+
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.annotation.Nullable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import static android.view.ViewGroup.LayoutParams.*;
+
+public class TestActivity extends Activity {
+
+ private ViewGroup content;
+ private Size contentSize = new Size(1000, 1000);
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ wakeScreen();
+
+ // Match parent decor view.
+ FrameLayout root = new FrameLayout(this);
+ root.setKeepScreenOn(true);
+ root.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ // Inner content view with fixed size.
+ // We want it to be fully visible or expresso will crash.
+ Point size = new Point();
+ getWindowManager().getDefaultDisplay().getSize(size);
+ int width = Math.min(size.x, size.y);
+ int height = Math.min(size.x, size.y);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ width, height, Gravity.CENTER);
+ content = new FrameLayout(this);
+ content.setLayoutParams(params);
+ contentSize = new Size(width, height);
+
+ // Add.
+ root.addView(content);
+ setContentView(root);
+ }
+
+ public void wakeScreen() {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
+ }
+
+ public Size getContentSize() {
+ return contentSize;
+ }
+
+ public ViewGroup getContentView() {
+ return content;
+ }
+
+ public void inflate(View child) {
+ inflate(child, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+
+ public void inflate(View child, ViewGroup.LayoutParams params) {
+ content.addView(child, params);
+ content.requestLayout();
+ }
+}
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureViewPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureViewPreviewTest.java
new file mode 100644
index 00000000..39391063
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureViewPreviewTest.java
@@ -0,0 +1,43 @@
+package com.otaliastudios.cameraview;
+
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.ViewGroup;
+
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TextureViewPreviewTest extends PreviewTest {
+
+ @Override
+ protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) {
+ return new TextureViewPreview(context, parent, callback);
+ }
+
+ @Override
+ protected void ensureAvailable() {
+ if (isHardwareAccelerated()) {
+ super.ensureAvailable();
+ } else {
+ preview.onSurfaceAvailable(
+ surfaceSize.getWidth(),
+ surfaceSize.getHeight());
+ }
+ }
+
+ @Override
+ protected void ensureDestroyed() {
+ super.ensureDestroyed();
+ if (!isHardwareAccelerated()) {
+ // Ensure it is called.
+ preview.onSurfaceDestroyed();
+ }
+ }
+
+ private boolean isHardwareAccelerated() {
+ return preview.getView().isHardwareAccelerated();
+ }
+}
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
index 3f8c79d7..a039ada2 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
@@ -2,6 +2,7 @@ package com.otaliastudios.cameraview;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.location.Location;
@@ -110,12 +111,14 @@ class Camera1 extends CameraController {
// These can happen at different times but we want to end up here.
private void setup() {
try {
+ Object output = mPreview.getOutput();
if (mPreview.getOutputClass() == SurfaceHolder.class) {
- mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
+ mCamera.setPreviewDisplay((SurfaceHolder) output);
} else {
- mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
+ mCamera.setPreviewTexture((SurfaceTexture) output);
}
} catch (IOException e) {
+ LOG.e("Error while trying to setup Camera1.", e);
throw new RuntimeException(e);
}
@@ -450,8 +453,10 @@ class Camera1 extends CameraController {
@Override
boolean shouldFlipSizes() {
+ int offset = computeSensorToDisplayOffset();
LOG.i("shouldFlip:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
- return (mDeviceOrientation + mSensorOffset) % 180 != 0;
+ LOG.i("shouldFlip:", "sensorToDisplay=", offset);
+ return offset % 180 != 0;
}
@Override
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
index 4e4de4ea..e79066a0 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
@@ -159,7 +159,7 @@ public class CameraView extends FrameLayout {
protected Preview instantiatePreview(Context context, ViewGroup container) {
- return new TextureViewPreview(context, container);
+ return new TextureViewPreview(context, container, null);
}
@@ -504,7 +504,7 @@ public class CameraView extends FrameLayout {
* @return true if we can go on, false otherwise.
*/
@SuppressLint("NewApi")
- private boolean checkPermissions(SessionType sessionType) {
+ protected boolean checkPermissions(SessionType sessionType) {
checkPermissionsManifestOrThrow(sessionType);
boolean api23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
int cameraCheck, audioCheck;
@@ -537,7 +537,7 @@ public class CameraView extends FrameLayout {
/**
* If mSessionType == SESSION_TYPE_VIDEO we will ask for RECORD_AUDIO permission.
* If the developer did not add this to its manifest, throw and fire warnings.
- * (Hoping this is not cought elsewhere... we should test).
+ * (Hoping this is not caught elsewhere... we should test).
*/
private void checkPermissionsManifestOrThrow(SessionType sessionType) {
if (sessionType == SessionType.VIDEO) {
diff --git a/cameraview/src/main/res/layout/surface_view.xml b/cameraview/src/main/res/layout/surface_view.xml
index 42fb3f23..3db4f4ea 100644
--- a/cameraview/src/main/res/layout/surface_view.xml
+++ b/cameraview/src/main/res/layout/surface_view.xml
@@ -3,8 +3,8 @@
+ android:layout_height="match_parent"
+ android:gravity="center" />
diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
index 53f9aaea..ba63b7f2 100644
--- a/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
+++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
@@ -19,11 +19,12 @@ class OrientationHelper {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
}
- private final OrientationEventListener mListener;
+ final OrientationEventListener mListener;
+ Display mDisplay;
+
private final Callbacks mCallbacks;
- private Display mDisplay;
- private int mLastKnownDisplayOffset = -1;
- private int mLastOrientation = -1;
+ int mLastKnownDisplayOffset = -1;
+ int mLastOrientation = -1;
interface Callbacks {
void onDisplayOffsetChanged(int displayOffset);
diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java
new file mode 100644
index 00000000..16ef3150
--- /dev/null
+++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java
@@ -0,0 +1,72 @@
+package com.otaliastudios.cameraview;
+
+import android.support.annotation.NonNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A naive implementation of {@link java.util.concurrent.CountDownLatch}
+ * to help in testing.
+ */
+class Task {
+
+ private CountDownLatch mLatch;
+ private T mResult;
+ private int mCount;
+
+ Task() {
+ }
+
+ private boolean listening() {
+ return mLatch != null;
+ }
+
+ void listen() {
+ if (listening()) throw new RuntimeException("Should not happen.");
+ mResult = null;
+ mLatch = new CountDownLatch(1);
+ }
+
+ void start() {
+ if (!listening()) mCount++;
+ }
+
+ void end(T result) {
+ if (mCount > 0) {
+ mCount--;
+ return;
+ }
+
+ if (listening()) { // Should be always true.
+ mResult = result;
+ mLatch.countDown();
+ }
+ }
+
+ T await() {
+ try {
+ mLatch.await();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ T result = mResult;
+ mResult = null;
+ mLatch = null;
+ return result;
+ }
+
+ T await(long time, @NonNull TimeUnit unit) {
+ try {
+ mLatch.await(time, unit);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ T result = mResult;
+ mResult = null;
+ mLatch = null;
+ return result;
+ }
+
+
+}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
index 5553b2da..6f1fcc36 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
@@ -39,6 +39,11 @@ abstract class GestureLayout extends FrameLayout {
return mType;
}
+ // For tests.
+ void setGestureType(Gesture type) {
+ mType = type;
+ }
+
public final PointF[] getPoints() {
return mPoints;
}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GridLinesLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GridLinesLayout.java
index d8f16e4f..0d7784ea 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/GridLinesLayout.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GridLinesLayout.java
@@ -16,10 +16,12 @@ class GridLinesLayout extends View {
private Grid gridMode;
- private final Drawable horiz;
- private final Drawable vert;
+ Drawable horiz;
+ Drawable vert;
private final float width;
+ Task drawTask = new Task<>();
+
private final static float GOLDEN_RATIO_INV = 0.61803398874989f;
public GridLinesLayout(@NonNull Context context) {
@@ -46,7 +48,7 @@ class GridLinesLayout extends View {
public void setGridMode(Grid gridMode) {
this.gridMode = gridMode;
- invalidate();
+ postInvalidate();
}
private int getLineCount() {
@@ -75,6 +77,7 @@ class GridLinesLayout extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
+ drawTask.start();
int count = getLineCount();
for (int n = 0; n < count; n++) {
float pos = getLinePosition(n);
@@ -89,5 +92,6 @@ class GridLinesLayout extends View {
vert.draw(canvas);
canvas.translate(- pos * getWidth(), 0);
}
+ drawTask.end(null);
}
}
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java
index 574c5e5a..08c0cd62 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java
@@ -14,7 +14,7 @@ class PinchGestureLayout extends GestureLayout {
private final static float ADD_SENSITIVITY = 2f;
- private ScaleGestureDetector mDetector;
+ ScaleGestureDetector mDetector;
private boolean mNotify;
private float mAdditionFactor = 0;
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
index 6e0c94bb..b8b0e58d 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
@@ -1,15 +1,17 @@
package com.otaliastudios.cameraview;
import android.content.Context;
-import android.graphics.SurfaceTexture;
+import android.support.annotation.NonNull;
import android.view.Surface;
-import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewGroup;
-abstract class Preview {
+abstract class Preview {
- private final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName());
+ protected final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName());
+
+ // Used for testing.
+ Task mCropTask = new Task<>();
// This is used to notify CameraImpl to recompute its camera Preview size.
// After that, CameraView will need a new layout pass to adapt to the Preview size.
@@ -19,6 +21,7 @@ abstract class Preview {
}
private SurfaceCallback mSurfaceCallback;
+ private T mView;
// As far as I can see, these are the view/surface dimensions.
// This live in the 'View' orientation.
@@ -29,19 +32,24 @@ abstract class Preview {
private int mDesiredWidth;
private int mDesiredHeight;
- Preview(Context context, ViewGroup parent) {}
+ Preview(Context context, ViewGroup parent, SurfaceCallback callback) {
+ mView = onCreateView(context, parent);
+ mSurfaceCallback = callback;
+ }
+
+ @NonNull
+ protected abstract T onCreateView(Context context, ViewGroup parent);
abstract Surface getSurface();
- abstract View getView();
- abstract Class getOutputClass();
- abstract boolean isReady();
- SurfaceHolder getSurfaceHolder() {
- return null;
- }
- SurfaceTexture getSurfaceTexture() {
- return null;
+
+ @NonNull
+ final T getView() {
+ return mView;
}
+ abstract Class