diff --git a/README.md b/README.md
index 6c00663a..d123e79a 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
CameraView is a well documented, high-level library that makes capturing pictures and videos easy, addressing most of the common issues and needs, and still leaving you with flexibility where needed.
```groovy
-compile 'com.otaliastudios:cameraview:1.2.0'
+compile 'com.otaliastudios:cameraview:1.2.1'
```
diff --git a/cameraview/build.gradle b/cameraview/build.gradle
index b23b1869..f212b60f 100644
--- a/cameraview/build.gradle
+++ b/cameraview/build.gradle
@@ -3,7 +3,7 @@ apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
// Required by bintray
-version = '1.2.0'
+version = '1.2.1'
group = 'com.otaliastudios'
//region android dependencies
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java
new file mode 100644
index 00000000..d0d52762
--- /dev/null
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java
@@ -0,0 +1,24 @@
+package com.otaliastudios.cameraview;
+
+
+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 WorkerHandlerTest {
+
+ @Test
+ public void testCache() {
+ WorkerHandler w1 = WorkerHandler.get("handler1");
+ WorkerHandler w1a = WorkerHandler.get("handler1");
+ WorkerHandler w2 = WorkerHandler.get("handler2");
+ assertTrue(w1 == w1a);
+ assertFalse(w1 == w2);
+ }
+}
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
index 8d842275..3f8c79d7 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
@@ -450,6 +450,7 @@ class Camera1 extends CameraController {
@Override
boolean shouldFlipSizes() {
+ LOG.i("shouldFlip:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
return (mDeviceOrientation + mSensorOffset) % 180 != 0;
}
@@ -510,7 +511,7 @@ class Camera1 extends CameraController {
// Choose the max size.
List captureSizes = sizesFromList(params.getSupportedPictureSizes());
Size maxSize = Collections.max(captureSizes);
- LOG.i("computeCaptureSize:", "computed", maxSize, "from", captureSizes);
+ LOG.i("computeCaptureSize:", "computed", maxSize);
return Collections.max(captureSizes);
} else {
// Choose according to developer choice in setVideoQuality.
@@ -768,7 +769,7 @@ class Camera1 extends CameraController {
for (Camera.Size size : sizes) {
result.add(new Size(size.width, size.height));
}
- LOG.i("sizesFromList:", result.toArray());
+ LOG.i("sizesFromList:", result);
return result;
}
@@ -802,13 +803,21 @@ class Camera1 extends CameraController {
LOG.i("matchSize:", "found big enough and consistent:", bigEnoughAndConsistent.size());
Size result;
if (biggestPossible) {
- if (bigEnoughAndConsistent.size() > 0) return Collections.max(bigEnoughAndConsistent);
- if (consistent.size() > 0) return Collections.max(consistent);
- result = Collections.max(sizes);
+ if (bigEnoughAndConsistent.size() > 0) {
+ result = Collections.max(bigEnoughAndConsistent);
+ } else if (consistent.size() > 0) {
+ result = Collections.max(consistent);
+ } else {
+ result = Collections.max(sizes);
+ }
} else {
- if (bigEnoughAndConsistent.size() > 0) return Collections.min(bigEnoughAndConsistent);
- if (consistent.size() > 0) return Collections.max(consistent);
- result = Collections.max(sizes);
+ if (bigEnoughAndConsistent.size() > 0) {
+ result = Collections.min(bigEnoughAndConsistent);
+ } else if (consistent.size() > 0) {
+ result = Collections.max(consistent);
+ } else {
+ result = Collections.max(sizes);
+ }
}
LOG.i("matchSize:", "returning result", result);
return result;
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
index 81d6dd7e..0432fcbc 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
@@ -35,7 +35,7 @@ abstract class CameraController implements Preview.SurfaceCallback {
mCameraCallbacks = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);
- mHandler = new WorkerHandler("CameraViewController");
+ mHandler = WorkerHandler.get("CameraViewController");
}
//region Start&Stop
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
index a5af9113..0a2723cb 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
@@ -117,7 +117,7 @@ public class CameraView extends FrameLayout {
mPreviewImpl = instantiatePreview(context, this);
mCameraController = instantiateCameraController(mCameraCallbacks, mPreviewImpl);
mUiHandler = new Handler(Looper.getMainLooper());
- mWorkerHandler = new WorkerHandler("CameraViewWorker");
+ mWorkerHandler = WorkerHandler.get("CameraViewWorker");
// Views
mGridLinesLayout = new GridLinesLayout(context);
diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java
index 798d8366..4d7cd897 100644
--- a/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java
+++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java
@@ -3,17 +3,43 @@ package com.otaliastudios.cameraview;
import android.os.Handler;
import android.os.HandlerThread;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
/**
* Class holding a background handler.
- * Using setDaemon right now but a better approach would be to have
- * start() and stop() callbacks here. TODO
+ * We want them to survive configuration changes if there's still job to do.
*/
class WorkerHandler {
+ private final static CameraLogger LOG = CameraLogger.create(WorkerHandler.class.getSimpleName());
+ private final static ConcurrentHashMap> sCache = new ConcurrentHashMap<>(4);
+
+ public static WorkerHandler get(String name) {
+ if (sCache.containsKey(name)) {
+ WorkerHandler cached = sCache.get(name).get();
+ if (cached != null) {
+ HandlerThread thread = cached.mThread;
+ if (thread.isAlive() && !thread.isInterrupted()) {
+ LOG.w("get:", "Reusing cached worker handler.", name);
+ return cached;
+ }
+ }
+ LOG.w("get:", "Thread reference died, removing.", name);
+ sCache.remove(name);
+ }
+
+ LOG.i("get:", "Creating new handler.", name);
+ WorkerHandler handler = new WorkerHandler(name);
+ sCache.put(name, new WeakReference<>(handler));
+ return handler;
+ }
+
private HandlerThread mThread;
private Handler mHandler;
- public WorkerHandler(String name) {
+ private WorkerHandler(String name) {
mThread = new HandlerThread(name);
mThread.setDaemon(true);
mThread.start();
diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
index a10addc1..6e0c94bb 100644
--- a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
+++ b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
@@ -9,6 +9,8 @@ import android.view.ViewGroup;
abstract class Preview {
+ private final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName());
+
// 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.
interface SurfaceCallback {
@@ -45,6 +47,7 @@ abstract class Preview {
// This is called by the CameraImpl.
// These must be alredy rotated, if needed, to be consistent with surface/view sizes.
void setDesiredSize(int width, int height) {
+ LOG.i("setDesiredSize:", "desiredW=", width, "desiredH=", height);
this.mDesiredWidth = width;
this.mDesiredHeight = height;
crop();
@@ -60,6 +63,7 @@ abstract class Preview {
protected final void onSurfaceAvailable(int width, int height) {
+ LOG.i("onSurfaceAvailable:", "w=", width, "h=", height);
mSurfaceWidth = width;
mSurfaceHeight = height;
crop();
@@ -70,6 +74,7 @@ abstract class Preview {
// As far as I can see, these are the view/surface dimensions.
// This is called by subclasses.
protected final void onSurfaceSizeChanged(int width, int height) {
+ LOG.i("onSurfaceSizeChanged:", "w=", width, "h=", height);
if (width != mSurfaceWidth || height != mSurfaceHeight) {
mSurfaceWidth = width;
mSurfaceHeight = height;
@@ -87,30 +92,33 @@ abstract class Preview {
/**
- * As far as I can see, this extends either width or height of the surface,
- * to match the desired aspect ratio.
- * This means that the external part of the surface will be cropped by the outer view.
+ * Here we must crop the visible part by applying a > 1 scale to one of our
+ * dimensions. This way our internal aspect ratio (mSurfaceWidth / mSurfaceHeight)
+ * will match the preview size aspect ratio (mDesiredWidth / mDesiredHeight).
+ *
+ * There might still be some absolute difference (e.g. same ratio but bigger / smaller).
+ * However that should be already managed by the framework.
*/
private final void crop() {
getView().post(new Runnable() {
@Override
public void run() {
- if (mDesiredWidth != 0 && mDesiredHeight != 0) {
- AspectRatio aspectRatio = AspectRatio.of(mDesiredWidth, mDesiredHeight);
- float targetHeight = (float) mSurfaceWidth / aspectRatio.toFloat();
- float scale = 1;
- if (mSurfaceHeight > 0) {
- scale = targetHeight / (float) mSurfaceHeight;
- }
-
- if (scale > 1) {
- getView().setScaleX(1f);
- getView().setScaleY(scale);
- } else {
- getView().setScaleX(1f / scale);
- getView().setScaleY(1f);
- }
+ if (mDesiredHeight == 0 || mDesiredWidth == 0) return;
+ if (mSurfaceHeight == 0 || mSurfaceWidth == 0) return;
+ float scaleX = 1f, scaleY = 1f;
+ AspectRatio current = AspectRatio.of(mSurfaceWidth, mSurfaceHeight);
+ AspectRatio target = AspectRatio.of(mDesiredWidth, mDesiredHeight);
+ if (current.toFloat() >= target.toFloat()) {
+ // We are too short. Must increase height.
+ scaleY = current.toFloat() / target.toFloat();
+ } else {
+ // We must increase width.
+ scaleX = target.toFloat() / current.toFloat();
}
+ getView().setScaleX(scaleX);
+ getView().setScaleY(scaleY);
+ LOG.i("crop:", "applied scaleX=", scaleX);
+ LOG.i("crop:", "applied scaleY=", scaleY);
}
});
}
diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml
index 4a44d44b..f3c04d1c 100644
--- a/demo/src/main/AndroidManifest.xml
+++ b/demo/src/main/AndroidManifest.xml
@@ -13,18 +13,18 @@
android:theme="@style/AppTheme">
+ android:name=".MainActivity"
+ android:theme="@style/Theme.MainActivity"
+ android:configChanges="orientation|screenLayout|keyboardHidden">
-
+
-
+