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"> - + - +