diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java index 65ff7ae33d..6686be4cc6 100644 --- a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java +++ b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.camera; import android.app.Activity; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -12,40 +13,46 @@ import android.view.Surface; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedList; import java.util.List; @SuppressWarnings("deprecation") public class CameraUtils { + private static final String TAG = CameraUtils.class.getSimpleName(); /* * modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java */ public static @Nullable Size getPreferredPreviewSize(int displayOrientation, int width, int height, - @NonNull Camera camera) { - Log.w("CameraUtils", String.format("getPreferredPreviewSize(%d, %d, %d)", displayOrientation, width, height)); - double targetRatio = (double)width / height; - Size optimalSize = null; - double minDiff = Double.MAX_VALUE; + @NonNull Parameters parameters) { + final int targetWidth = displayOrientation % 180 == 90 ? height : width; + final int targetHeight = displayOrientation % 180 == 90 ? width : height; + final double targetRatio = (double) targetWidth / targetHeight; - if (displayOrientation == 90 || displayOrientation == 270) { - targetRatio = (double)height / width; - } + Log.w(TAG, String.format("getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f", + displayOrientation, width, height, + targetWidth, targetHeight, targetRatio)); - List sizes = camera.getParameters().getSupportedPreviewSizes(); - - Collections.sort(sizes, Collections.reverseOrder(new SizeComparator())); + List sizes = parameters.getSupportedPreviewSizes(); + List ideals = new LinkedList<>(); + List bigEnough = new LinkedList<>(); for (Size size : sizes) { - double ratio = (double)size.width / size.height; + Log.w(TAG, String.format(" %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height)); - if (Math.abs(ratio - targetRatio) < minDiff) { - optimalSize = size; - minDiff = Math.abs(ratio - targetRatio); + if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) { + ideals.add(size); + Log.w(TAG, " (ideal ratio)"); + } else if (size.width >= targetWidth && size.height >= targetHeight) { + bigEnough.add(size); + Log.w(TAG, " (good size, suboptimal ratio)"); } } - return optimalSize; + if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator()); + else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio)); + else return Collections.max(sizes, new AreaComparator()); } // based on @@ -74,15 +81,26 @@ public class CameraUtils { } } - private static class SizeComparator implements Comparator { + private static class AreaComparator implements Comparator { @Override public int compare(Size lhs, Size rhs) { - int left = lhs.width * lhs.height; - int right = rhs.width * rhs.height; + return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height); + } + } - if (left < right) return -1; - if (left > right) return 1; - else return 0; + private static class AspectRatioComparator extends AreaComparator { + private final double target; + public AspectRatioComparator(double target) { + this.target = target; + } + + @Override + public int compare(Size lhs, Size rhs) { + final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height); + final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height); + if (lhsDiff < rhsDiff) return -1; + else if (lhsDiff > rhsDiff) return 1; + else return super.compare(lhs, rhs); } } } diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java index 32bc036987..bb4ca73144 100644 --- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java +++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java @@ -58,10 +58,11 @@ public class CameraView extends FrameLayout { private @NonNull volatile Optional camera = Optional.absent(); private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK; - private boolean started; + private @NonNull State state = State.PAUSED; + private @Nullable Size previewSize; private @Nullable CameraViewListener listener; - private int displayOrientation = -1; - private int outputOrientation = -1; + private int displayOrientation = -1; + private int outputOrientation = -1; public CameraView(Context context) { this(context, null); @@ -92,8 +93,8 @@ public class CameraView extends FrameLayout { @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void onResume() { - if (started) return; - started = true; + if (state != State.PAUSED) return; + state = State.RESUMED; Log.w(TAG, "onResume() queued"); enqueueTask(new SerialAsyncTask() { @Override @@ -125,8 +126,6 @@ public class CameraView extends FrameLayout { synchronized (CameraView.this) { CameraView.this.notifyAll(); } - requestLayout(); - invalidate(); Log.w(TAG, "onResume() completed"); } catch (RuntimeException e) { Log.w(TAG, "exception when starting camera preview", e); @@ -137,8 +136,8 @@ public class CameraView extends FrameLayout { } public void onPause() { - if (!started) return; - started = false; + if (state == State.PAUSED) return; + state = State.PAUSED; Log.w(TAG, "onPause() queued"); enqueueTask(new SerialAsyncTask() { @@ -175,28 +174,7 @@ public class CameraView extends FrameLayout { } public boolean isStarted() { - return started; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera.isPresent()) { - final Size preferredPreviewSize = CameraUtils.getPreferredPreviewSize(displayOrientation, - getMeasuredWidth(), - getMeasuredHeight(), - camera.get()); - final Parameters parameters = camera.get().getParameters(); - if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) { - Log.w(TAG, "setting preview size to " + preferredPreviewSize.width + "x" + preferredPreviewSize.height); - stopPreview(); - parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height); - camera.get().setParameters(parameters); - requestLayout(); - startPreview(); - } - } + return state != State.PAUSED; } @SuppressWarnings("SuspiciousNameCombination") @@ -207,8 +185,7 @@ public class CameraView extends FrameLayout { final int previewWidth; final int previewHeight; - if (camera.isPresent()) { - final Size previewSize = camera.get().getParameters().getPreviewSize(); + if (camera.isPresent() && previewSize != null) { if (displayOrientation == 90 || displayOrientation == 270) { previewWidth = previewSize.height; previewHeight = previewSize.width; @@ -225,7 +202,6 @@ public class CameraView extends FrameLayout { Log.w(TAG, "skipping layout due to zero-width/height preview size"); return; } - Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight); if (width * previewHeight > height * previewWidth) { final int scaledChildHeight = previewHeight * width / previewWidth; @@ -236,11 +212,18 @@ public class CameraView extends FrameLayout { } } + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + Log.w(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")"); + super.onSizeChanged(w, h, oldw, oldh); + if (camera.isPresent()) startPreview(camera.get().getParameters()); + } + public void setListener(@Nullable CameraViewListener listener) { this.listener = listener; } - public void setPreviewCallback(final PreviewCallback previewCallback) { + public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) { enqueueTask(new PostInitializationTask() { @Override protected void onPostMain(Void avoid) { @@ -252,8 +235,8 @@ public class CameraView extends FrameLayout { return; } - final int rotation = getCameraPictureOrientation(); - final Size previewSize = camera.getParameters().getPreviewSize(); + final int rotation = getCameraPictureOrientation(); + final Size previewSize = camera.getParameters().getPreviewSize(); if (data != null) { previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation)); } @@ -308,6 +291,7 @@ public class CameraView extends FrameLayout { if (camera.isPresent()) { try { camera.get().setPreviewDisplay(surface.getHolder()); + startPreview(parameters); requestLayout(); } catch (Exception e) { Log.w(TAG, e); @@ -317,10 +301,24 @@ public class CameraView extends FrameLayout { }); } - private void startPreview() { - if (camera.isPresent()) { + private void startPreview(final @NonNull Parameters parameters) { + if (this.camera.isPresent()) { try { - camera.get().startPreview(); + final Camera camera = this.camera.get(); + final Size preferredPreviewSize = getPreferredPreviewSize(parameters); + + if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) { + Log.w(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height); + if (state == State.ACTIVE) stopPreview(); + previewSize = preferredPreviewSize; + parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height); + camera.setParameters(parameters); + } else { + previewSize = parameters.getPreviewSize(); + } + camera.startPreview(); + requestLayout(); + state = State.ACTIVE; } catch (Exception e) { Log.w(TAG, e); } @@ -331,13 +329,21 @@ public class CameraView extends FrameLayout { if (camera.isPresent()) { try { camera.get().stopPreview(); + state = State.RESUMED; } catch (Exception e) { Log.w(TAG, e); } } } - public int getCameraPictureOrientation() { + private Size getPreferredPreviewSize(@NonNull Parameters parameters) { + return CameraUtils.getPreferredPreviewSize(displayOrientation, + getMeasuredWidth(), + getMeasuredHeight(), + parameters); + } + + private int getCameraPictureOrientation() { if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { outputOrientation = getCameraPictureRotation(getActivity().getWindowManager() .getDefaultDisplay() @@ -589,4 +595,8 @@ public class CameraView extends FrameLayout { return orientation; } } + + private enum State { + PAUSED, RESUMED, ACTIVE + } }