diff --git a/build.gradle b/build.gradle index aa754d7d10..2137d0d309 100644 --- a/build.gradle +++ b/build.gradle @@ -29,9 +29,6 @@ repositories { maven { // textdrawable url 'https://dl.bintray.com/amulyakhare/maven' } - maven { // cwac-camera - url 'https://repo.commonsware.com.s3.amazonaws.com' - } jcenter() mavenLocal() } @@ -72,7 +69,6 @@ dependencies { exclude group: 'com.android.support', module: 'support-v4' } compile 'com.madgag.spongycastle:prov:1.51.0.0' - compile 'com.commonsware.cwac:camera:0.6.12' provided 'com.squareup.dagger:dagger-compiler:1.2.2' compile 'org.whispersystems:jobmanager:1.0.2' @@ -126,7 +122,6 @@ dependencyVerification { 'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883', 'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d', 'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a', - 'com.commonsware.cwac:camera:dcc93ddbb2f0393114fa1f31a13fe9e6edfcf5dbe96b22bc4b66c7b15e179054', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', diff --git a/res/layout/quick_attachment_drawer.xml b/res/layout/quick_attachment_drawer.xml index 1bb3907592..62ec6213ac 100644 --- a/res/layout/quick_attachment_drawer.xml +++ b/res/layout/quick_attachment_drawer.xml @@ -1,7 +1,7 @@ - diff --git a/res/layout/quick_camera_controls_land.xml b/res/layout/quick_camera_controls_land.xml index 2fc926f310..b00711eb4d 100644 --- a/res/layout/quick_camera_controls_land.xml +++ b/res/layout/quick_camera_controls_land.xml @@ -31,7 +31,7 @@ android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:background="#00000000" - android:src="@drawable/quick_camera_front" + android:src="@drawable/quick_camera_rear" android:padding="20dp" android:visibility="invisible" tools:visibility="visible"/> diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 77d2e3deb2..98687697c7 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -55,7 +55,6 @@ import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.AlertDialogWrapper; -import com.commonsware.cwac.camera.CameraHost.FailureReason; import com.google.protobuf.ByteString; import org.thoughtcrime.redphone.RedPhone; @@ -1344,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } @Override - public void onCameraFail(FailureReason reason) { + public void onCameraFail() { Toast.makeText(this, R.string.ConversationActivity_quick_camera_unavailable, Toast.LENGTH_SHORT).show(); quickAttachmentDrawer.hide(false); quickAttachmentToggle.disable(); diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java b/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java new file mode 100644 index 0000000000..7a991eae5c --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.components.camera; + +import android.content.Context; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + private boolean ready; + + @SuppressWarnings("deprecation") + public CameraSurfaceView(Context context) { + super(context); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + getHolder().addCallback(this); + } + + public boolean isReady() { + return ready; + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + ready = true; + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + ready = false; + } +} diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java new file mode 100644 index 0000000000..9ee2b3d218 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java @@ -0,0 +1,70 @@ +package org.thoughtcrime.securesms.components.camera; + +import android.annotation.TargetApi; +import android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.os.Build.VERSION; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@SuppressWarnings("deprecation") +public class CameraUtils { + @TargetApi(11) + public static @Nullable Size getPreferredPreviewSize(int orientation, int width, int height, @NonNull Camera camera) { + final Parameters parameters = camera.getParameters(); + final Size preferredSize = VERSION.SDK_INT > 11 + ? parameters.getPreferredPreviewSizeForVideo() + : null; + + return preferredSize == null ? getBestAspectPreviewSize(orientation, width, height, parameters) + : preferredSize; + } + + /* + * modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java + */ + public static @Nullable Size getBestAspectPreviewSize(int displayOrientation, + int width, + int height, + Parameters parameters) { + double targetRatio = (double)width / height; + Size optimalSize = null; + double minDiff = Double.MAX_VALUE; + + if (displayOrientation == 90 || displayOrientation == 270) { + targetRatio = (double)height / width; + } + + List sizes = parameters.getSupportedPreviewSizes(); + + Collections.sort(sizes, Collections.reverseOrder(new SizeComparator())); + + for (Size size : sizes) { + double ratio = (double)size.width / size.height; + + if (Math.abs(ratio - targetRatio) < minDiff) { + optimalSize = size; + minDiff = Math.abs(ratio - targetRatio); + } + } + + return optimalSize; + } + + private static class SizeComparator implements Comparator { + @Override + public int compare(Size lhs, Size rhs) { + int left = lhs.width * lhs.height; + int right = rhs.width * rhs.height; + + if (left < right) return -1; + if (left > right) return 1; + else return 0; + } + } +} diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java index 6be20ebb82..8c3ff03857 100644 --- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java +++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java @@ -20,44 +20,47 @@ import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Color; +import android.graphics.Rect; import android.hardware.Camera; -import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.os.AsyncTask; import android.os.Build; +import android.os.Build.VERSION; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.OrientationEventListener; import android.view.Surface; -import android.view.View; import android.widget.FrameLayout; import java.io.IOException; -import java.util.concurrent.CountDownLatch; - -import com.commonsware.cwac.camera.CameraHost; -import com.commonsware.cwac.camera.CameraHost.FailureReason; +import java.util.List; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.Job; import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.util.guava.Optional; @SuppressWarnings("deprecation") public class CameraView extends FrameLayout { private static final String TAG = CameraView.class.getSimpleName(); - private PreviewStrategy previewStrategy = null; - private Camera.Size previewSize = null; - private volatile Camera camera = null; - private boolean inPreview = false; - private boolean cameraReady = false; - private CameraHost host = null; - private OnOrientationChange onOrientationChange = null; - private int displayOrientation = -1; - private int outputOrientation = -1; - private int cameraId = -1; - private int lastPictureOrientation = -1; + private final CameraSurfaceView surface; + private final OnOrientationChange onOrientationChange; + + private @NonNull volatile Optional camera = Optional.absent(); + private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK; + + private boolean started; + private @Nullable CameraViewListener listener; + private int displayOrientation = -1; + private int outputOrientation = -1; public CameraView(Context context) { this(context, null); @@ -71,51 +74,41 @@ public class CameraView extends FrameLayout { super(context, attrs, defStyle); setBackgroundColor(Color.BLACK); + if (isMultiCamera()) cameraId = CameraInfo.CAMERA_FACING_FRONT; + + surface = new CameraSurfaceView(getContext()); onOrientationChange = new OnOrientationChange(context.getApplicationContext()); - } - - public CameraHost getHost() { - return host; - } - - public void setHost(CameraHost host) { - this.host = host; - - if (host.getDeviceProfile().useTextureView()) { - previewStrategy = new TexturePreviewStrategy(this); - } else { - previewStrategy = new SurfacePreviewStrategy(this); - } - addView(previewStrategy.getWidget()); + addView(surface); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void onResume() { + if (started) return; + started = true; Log.w(TAG, "onResume() queued"); - final CameraHost host = getHost(); - submitTask(new SerializedAsyncTask() { - @Override protected FailureReason onRunBackground() { + enqueueTask(new SerialAsyncTask() { + @Override + protected @Nullable Camera onRunBackground() { try { - cameraId = host.getCameraId(); if (cameraId >= 0) { - camera = Camera.open(cameraId); + return Camera.open(cameraId); } else { - return FailureReason.NO_CAMERAS_REPORTED; + return null; } } catch (Exception e) { - return FailureReason.UNKNOWN; + return null; } - - return null; } - @Override protected void onPostMain(FailureReason result) { - if (result != null) { - host.onCameraFail(result); + @Override + protected void onPostMain(@Nullable Camera camera) { + if (camera == null) { + if (listener != null) listener.onCameraFail(); return; } + + CameraView.this.camera = Optional.of(camera); try { - cameraReady = true; if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { onOrientationChange.enable(); } @@ -123,227 +116,182 @@ public class CameraView extends FrameLayout { synchronized (CameraView.this) { CameraView.this.notifyAll(); } - previewCreated(); - initPreview(); + onCameraReady(); requestLayout(); invalidate(); Log.w(TAG, "onResume() completed"); - } catch (RuntimeException re) { - Log.w(TAG, "exception when starting camera preview", re); - try { - previewDestroyed(); - } catch (RuntimeException re2) { - Log.w(TAG, "also failed to release camera", re2); - } + } catch (RuntimeException e) { + Log.w(TAG, "exception when starting camera preview", e); + onPause(); } } }); } public void onPause() { + if (!started) return; + started = false; Log.w(TAG, "onPause() queued"); - submitTask(new SerializedAsyncTask() { + final Optional cameraToDestroy = camera; + + enqueueTask(new SerialAsyncTask() { @Override protected void onPreMain() { - cameraReady = false; + camera = Optional.absent(); } @Override protected Void onRunBackground() { - previewDestroyed(); + if (cameraToDestroy.isPresent()) { + stopPreview(); + cameraToDestroy.get().release(); + } return null; } @Override protected void onPostMain(Void avoid) { onOrientationChange.disable(); - previewSize = null; displayOrientation = -1; outputOrientation = -1; - cameraId = -1; - lastPictureOrientation = -1; Log.w(TAG, "onPause() completed"); } }); } - // based on CameraPreview.java from ApiDemos + public boolean isStarted() { + return started; + } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) { - Camera.Size newSize = null; - - try { - if (getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY) { - newSize = getHost().getPreferredPreviewSizeForVideo(getDisplayOrientation(), - getMeasuredWidth(), - getMeasuredHeight(), - camera.getParameters(), - null); - } - if (newSize == null || newSize.width * newSize.height < 65536) { - newSize = getHost().getPreviewSize(getDisplayOrientation(), - getMeasuredWidth(), - getMeasuredHeight(), - camera.getParameters()); - } - } catch (Exception e) { - Log.e(TAG, "Could not work with camera parameters?", e); - // TODO get this out to library clients - } - - if (newSize != null) { - if (previewSize == null) { - previewSize = newSize; - synchronized (this) { notifyAll(); } - } else if (previewSize.width != newSize.width || previewSize.height != newSize.height) { - if (inPreview) { - stopPreview(); - } - - previewSize = newSize; - synchronized (this) { notifyAll(); } - initPreview(); - } + 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)) { + stopPreview(); + parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height); + camera.get().setParameters(parameters); + requestLayout(); + startPreview(); } } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - // based on CameraPreview.java from ApiDemos - - @SuppressWarnings("SuspiciousNameCombination") @Override + @SuppressWarnings("SuspiciousNameCombination") + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - if (getChildCount() > 0) { - final View child = getChildAt(0); - final int width = r - l; - final int height = b - t; - final int previewWidth; - final int previewHeight; + final int width = r - l; + final int height = b - t; + final int previewWidth; + final int previewHeight; - // handle orientation - - if (previewSize != null && (getDisplayOrientation() == 90 || getDisplayOrientation() == 270)) { + if (camera.isPresent()) { + final Size previewSize = camera.get().getParameters().getPreviewSize(); + if (displayOrientation == 90 || displayOrientation == 270) { previewWidth = previewSize.height; previewHeight = previewSize.width; - } else if (previewSize != null) { + } else { previewWidth = previewSize.width; previewHeight = previewSize.height; - } else { - previewWidth = width; - previewHeight = height; } + } else { + previewWidth = width; + previewHeight = height; + } - if (previewHeight == 0 || previewWidth == 0) { - Log.w(TAG, "skipping layout due to zero-width/height preview size"); - return; - } + if (previewHeight == 0 || previewWidth == 0) { + Log.w(TAG, "skipping layout due to zero-width/height preview size"); + return; + } + Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight); - boolean useFirstStrategy = (width * previewHeight > height * previewWidth); - boolean useFullBleed = getHost().useFullBleedPreview(); - - if ((useFirstStrategy && !useFullBleed) || (!useFirstStrategy && useFullBleed)) { - final int scaledChildWidth = previewWidth * height / previewHeight; - child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); - } else { - final int scaledChildHeight = previewHeight * width / previewWidth; - child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); - } + if (width * previewHeight > height * previewWidth) { + final int scaledChildHeight = previewHeight * width / previewWidth; + surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); + } else { + final int scaledChildWidth = previewWidth * height / previewHeight; + surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); } } - public int getDisplayOrientation() { - return displayOrientation; + public void setListener(@Nullable CameraViewListener listener) { + this.listener = listener; } - public void setOneShotPreviewCallback(PreviewCallback callback) { - if (camera != null) camera.setOneShotPreviewCallback(callback); + public boolean isMultiCamera() { + return Camera.getNumberOfCameras() > 1; } - public @Nullable Camera.Parameters getCameraParameters() { - return camera == null || !cameraReady ? null : camera.getParameters(); + public boolean isRearCamera() { + return cameraId == CameraInfo.CAMERA_FACING_BACK; } - void previewCreated() { - Log.w(TAG, "previewCreated() queued"); - final CameraHost host = getHost(); - submitTask(new PostInitializationTask() { + public void flipCamera() { + if (Camera.getNumberOfCameras() > 1) { + cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK + ? CameraInfo.CAMERA_FACING_FRONT + : CameraInfo.CAMERA_FACING_BACK; + onPause(); + onResume(); + } + } + + + @TargetApi(14) + private void onCameraReady() { + if (!camera.isPresent()) return; + + final Parameters parameters = camera.get().getParameters(); + final List focusModes = parameters.getSupportedFocusModes(); + + if (VERSION.SDK_INT >= 14) parameters.setRecordingHint(true); + + if (VERSION.SDK_INT >= 14 && focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + } else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { + parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); + } + + camera.get().setParameters(parameters); + + enqueueTask(new PostInitializationTask() { @Override protected void onPostMain(Void avoid) { - try { - if (camera != null) { - previewStrategy.attach(camera); + if (camera.isPresent()) { + try { + camera.get().setPreviewDisplay(surface.getHolder()); + startPreview(); + } catch (IOException e) { + Log.w(TAG, e); } - } catch (IOException e) { - host.handleException(e); - } - Log.w(TAG, "previewCreated() completed"); - } - }); - } - - void previewDestroyed() { - try { - if (camera != null) { - previewStopped(); - camera.release(); - } - } finally { - camera = null; - } - } - - private void previewStopped() { - if (inPreview) { - stopPreview(); - } - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - public void initPreview() { - Log.w(TAG, "initPreview() queued"); - submitTask(new PostInitializationTask() { - @Override protected void onPostMain(Void avoid) { - if (camera != null && cameraReady) { - Camera.Parameters parameters = camera.getParameters(); - - parameters.setPreviewSize(previewSize.width, previewSize.height); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - parameters.setRecordingHint(getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY); - } - - camera.setParameters(getHost().adjustPreviewParameters(parameters)); - startPreview(); - requestLayout(); - invalidate(); - Log.w(TAG, "initPreview() completed"); } } }); } private void startPreview() { - camera.startPreview(); - inPreview = true; - getHost().autoFocusAvailable(); + if (camera.isPresent()) { + camera.get().startPreview(); + } } private void stopPreview() { - camera.startPreview(); - inPreview = false; - getHost().autoFocusUnavailable(); - camera.stopPreview(); + if (camera.isPresent()) { + camera.get().stopPreview(); + } } // based on // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int) // and http://stackoverflow.com/a/10383164/115145 private void setCameraDisplayOrientation() { - Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.CameraInfo info = getCameraInfo(); int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; DisplayMetrics dm = new DisplayMetrics(); - Camera.getCameraInfo(cameraId, info); getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); switch (rotation) { @@ -361,64 +309,51 @@ public class CameraView extends FrameLayout { displayOrientation = (info.orientation - degrees + 360) % 360; } - boolean wasInPreview = inPreview; - - if (inPreview) { - stopPreview(); - } - - camera.setDisplayOrientation(displayOrientation); - - if (wasInPreview) { - startPreview(); - } + stopPreview(); + camera.get().setDisplayOrientation(displayOrientation); + startPreview(); } public int getCameraPictureOrientation() { - Camera.CameraInfo info = new Camera.CameraInfo(); - - Camera.getCameraInfo(cameraId, info); - if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { outputOrientation = getCameraPictureRotation(getActivity().getWindowManager() .getDefaultDisplay() .getOrientation()); - } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + } else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) { outputOrientation = (360 - displayOrientation) % 360; } else { outputOrientation = displayOrientation; } - if (lastPictureOrientation != outputOrientation) { - lastPictureOrientation = outputOrientation; - } return outputOrientation; } - // based on: - // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setRotation(int) + private @NonNull CameraInfo getCameraInfo() { + final CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraId, info); + return info; + } + + // XXX this sucks + private Activity getActivity() { + return (Activity)getContext(); + } public int getCameraPictureRotation(int orientation) { - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - int rotation; + final CameraInfo info = getCameraInfo(); + final int rotation; orientation = (orientation + 45) / 90 * 90; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { rotation = (info.orientation - orientation + 360) % 360; - } - else { // back-facing camera + } else { rotation = (info.orientation + orientation) % 360; } return rotation; } - Activity getActivity() { - return (Activity)getContext(); - } - private class OnOrientationChange extends OrientationEventListener { public OnOrientationChange(Context context) { super(context); @@ -427,19 +362,18 @@ public class CameraView extends FrameLayout { @Override public void onOrientationChanged(int orientation) { - if (camera != null && orientation != ORIENTATION_UNKNOWN) { + if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) { int newOutputOrientation = getCameraPictureRotation(orientation); if (newOutputOrientation != outputOrientation) { outputOrientation = newOutputOrientation; - Camera.Parameters params = camera.getParameters(); + Camera.Parameters params = camera.get().getParameters(); params.setRotation(outputOrientation); try { - camera.setParameters(params); - lastPictureOrientation = outputOrientation; + camera.get().setParameters(params); } catch (Exception e) { Log.e(TAG, "Exception updating camera parameters in orientation change", e); @@ -449,13 +383,66 @@ public class CameraView extends FrameLayout { } } - private void submitTask(SerializedAsyncTask job) { + public void takePicture(final Rect previewRect) { + if (!camera.isPresent() || camera.get().getParameters() == null) { + Log.w(TAG, "camera not in capture-ready state"); + return; + } + + camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() { + @Override + public void onPreviewFrame(byte[] data, final Camera camera) { + final int rotation = getCameraPictureOrientation(); + final Size previewSize = camera.getParameters().getPreviewSize(); + final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation); + + Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height); + Log.w(TAG, "data bytes: " + data.length); + Log.w(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat()); + Log.w(TAG, "croppingRect: " + croppingRect.toString()); + Log.w(TAG, "rotation: " + rotation); + new RotatePreviewAsyncTask(previewSize, rotation, croppingRect).execute(data); + } + }); + } + + private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) { + final int previewWidth = cameraPreviewSize.width; + final int previewHeight = cameraPreviewSize.height; + + if (rotation % 180 > 0) rotateRect(visibleRect); + + float scale = (float) previewWidth / visibleRect.width(); + if (visibleRect.height() * scale > previewHeight) { + scale = (float) previewHeight / visibleRect.height(); + } + final float newWidth = visibleRect.width() * scale; + final float newHeight = visibleRect.height() * scale; + final float centerX = (VERSION.SDK_INT < 14) ? previewWidth - newWidth / 2 : previewWidth / 2; + final float centerY = previewHeight / 2; + + visibleRect.set((int) (centerX - newWidth / 2), + (int) (centerY - newHeight / 2), + (int) (centerX + newWidth / 2), + (int) (centerY + newHeight / 2)); + + if (rotation % 180 > 0) rotateRect(visibleRect); + return visibleRect; + } + + + @SuppressWarnings("SuspiciousNameCombination") + private void rotateRect(Rect rect) { + rect.set(rect.top, rect.left, rect.bottom, rect.right); + } + + private void enqueueTask(SerialAsyncTask job) { ApplicationContext.getInstance(getContext()).getJobManager().add(job); } - private static abstract class SerializedAsyncTask extends Job { + private static abstract class SerialAsyncTask extends Job { - public SerializedAsyncTask() { + public SerialAsyncTask() { super(JobParameters.newBuilder().withGroupId(CameraView.class.getSimpleName()).create()); } @@ -464,7 +451,7 @@ public class CameraView extends FrameLayout { @Override public final void onRun() { try { onWait(); - runOnMainSync(new Runnable() { + Util.runOnMainSync(new Runnable() { @Override public void run() { onPreMain(); } @@ -472,7 +459,7 @@ public class CameraView extends FrameLayout { final Result result = onRunBackground(); - runOnMainSync(new Runnable() { + Util.runOnMainSync(new Runnable() { @Override public void run() { onPostMain(result); } @@ -488,44 +475,61 @@ public class CameraView extends FrameLayout { @Override public void onCanceled() { } - private void runOnMainSync(final Runnable runnable) { - final CountDownLatch sync = new CountDownLatch(1); - Util.runOnMain(new Runnable() { - @Override public void run() { - try { - runnable.run(); - } finally { - sync.countDown(); - } - } - }); - try { - sync.await(); - } catch (InterruptedException ie) { - throw new AssertionError(ie); - } - } - protected void onWait() throws PreconditionsNotMetException {} protected void onPreMain() {} protected Result onRunBackground() { return null; } protected void onPostMain(Result result) {} } - private abstract class PostInitializationTask extends SerializedAsyncTask { + private abstract class PostInitializationTask extends SerialAsyncTask { @Override protected void onWait() throws PreconditionsNotMetException { synchronized (CameraView.this) { - if (!cameraReady) { + if (!camera.isPresent()) { throw new PreconditionsNotMetException(); } - while (camera == null || previewSize == null || !previewStrategy.isReady()) { - Log.w(TAG, String.format("waiting. camera? %s previewSize? %s prevewStrategy? %s", - camera != null, previewSize != null, previewStrategy.isReady())); + while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) { + Log.w(TAG, String.format("waiting. surface ready? %s", surface.isReady())); Util.wait(CameraView.this, 0); } } } } + private class RotatePreviewAsyncTask extends AsyncTask { + private final Size previewSize; + private final int rotation; + private final Rect croppingRect; + + public RotatePreviewAsyncTask(Size previewSize, int rotation, Rect croppingRect) { + this.previewSize = previewSize; + this.rotation = rotation; + this.croppingRect = croppingRect; + } + + @Override + protected byte[] doInBackground(byte[]... params) { + final byte[] data = params[0]; + try { + return BitmapUtil.createFromNV21(data, + previewSize.width, + previewSize.height, + rotation, + croppingRect); + } catch (IOException e) { + return null; + } + } + + @Override + protected void onPostExecute(byte[] imageBytes) { + if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes); + } + } + private static class PreconditionsNotMetException extends Exception {} + + public interface CameraViewListener { + void onImageCapture(@NonNull final byte[] imageBytes); + void onCameraFail(); + } } diff --git a/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java deleted file mode 100644 index 14ce75f486..0000000000 --- a/src/org/thoughtcrime/securesms/components/camera/PreviewStrategy.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.thoughtcrime.securesms.components.camera; - -import android.hardware.Camera; -import android.media.MediaRecorder; -import android.view.View; - -import java.io.IOException; - -@SuppressWarnings("deprecation") -public interface PreviewStrategy extends com.commonsware.cwac.camera.PreviewStrategy { - boolean isReady(); -} diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java index d62e272d70..d20dbf65b4 100644 --- a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java +++ b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java @@ -26,7 +26,7 @@ import com.nineoldandroids.animation.ObjectAnimator; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.InputAwareLayout.InputView; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout; -import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener; +import org.thoughtcrime.securesms.components.camera.CameraView.CameraViewListener; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -36,7 +36,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { private final ViewDragHelper dragHelper; - private QuickCamera quickCamera; + private CameraView cameraView; private int coverViewPosition; private KeyboardAwareLinearLayout container; private View coverView; @@ -74,12 +74,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { private void initializeView() { inflate(getContext(), R.layout.quick_attachment_drawer, this); - quickCamera = (QuickCamera) findViewById(R.id.quick_camera); + cameraView = ViewUtil.findById(this, R.id.quick_camera); updateControlsView(); coverViewPosition = getChildCount(); controls.setVisibility(GONE); - quickCamera.setVisibility(GONE); + cameraView.setVisibility(GONE); } public static boolean isDeviceSupported(Context context) { @@ -108,7 +108,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { this.rotation = rotation; if (rotationChanged) { if (isShowing()) { - quickCamera.onPause(); + cameraView.onPause(); } updateControlsView(); setDrawerStateAndUpdate(drawerState, true); @@ -123,13 +123,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { shutterButton = (ImageButton) controls.findViewById(R.id.shutter_button); swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button); fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button); - if (quickCamera.isMultipleCameras()) { + if (cameraView.isMultiCamera()) { swapCameraButton.setVisibility(View.VISIBLE); swapCameraButton.setOnClickListener(new CameraFlipClickListener()); } shutterButton.setOnClickListener(new ShutterClickListener()); fullScreenButton.setOnClickListener(new FullscreenClickListener()); - ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(quickCamera) + 1); + ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(cameraView) + 1); this.controls = controls; } @@ -170,11 +170,11 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { int childLeft = paddingLeft; int childBottom; - if (child == quickCamera) { + if (child == cameraView) { childTop = computeCameraTopPosition(slideOffset); childBottom = childTop + childHeight; - if (quickCamera.getMeasuredWidth() < getMeasuredWidth()) - childLeft = (getMeasuredWidth() - quickCamera.getMeasuredWidth()) / 2 + paddingLeft; + if (cameraView.getMeasuredWidth() < getMeasuredWidth()) + childLeft = (getMeasuredWidth() - cameraView.getMeasuredWidth()) / 2 + paddingLeft; } else if (child == controls) { childBottom = getMeasuredHeight(); } else { @@ -271,14 +271,14 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { ViewCompat.postInvalidateOnAnimation(this); } - if (slideOffset == 0 && quickCamera.isStarted()) { - quickCamera.onPause(); + if (slideOffset == 0 && cameraView.isStarted()) { + cameraView.onPause(); controls.setVisibility(GONE); - quickCamera.setVisibility(GONE); - } else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) { + cameraView.setVisibility(GONE); + } else if (slideOffset != 0 && !cameraView.isStarted() & !paused) { controls.setVisibility(VISIBLE); - quickCamera.setVisibility(VISIBLE); - quickCamera.onResume(); + cameraView.setVisibility(VISIBLE); + cameraView.onResume(); } } @@ -335,10 +335,10 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { public void setListener(AttachmentDrawerListener listener) { this.listener = listener; - if (quickCamera != null) quickCamera.setQuickCameraListener(listener); + if (cameraView != null) cameraView.setListener(listener); } - public interface AttachmentDrawerListener extends QuickCameraListener { + public interface AttachmentDrawerListener extends CameraViewListener { void onAttachmentDrawerStateChanged(DrawerState drawerState); } @@ -391,8 +391,8 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { int slideOffset = getTargetSlideOffset(); dragHelper.captureChildView(coverView, 0); dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset)); - dragHelper.captureChildView(quickCamera, 0); - dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); + dragHelper.captureChildView(cameraView, 0); + dragHelper.settleCapturedViewAt(cameraView.getLeft(), computeCameraTopPosition(slideOffset)); ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this); } } @@ -455,13 +455,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { @SuppressWarnings("ResourceType") private boolean isDragViewUnder(int x, int y) { int[] viewLocation = new int[2]; - quickCamera.getLocationOnScreen(viewLocation); + cameraView.getLocationOnScreen(viewLocation); int[] parentLocation = new int[2]; this.getLocationOnScreen(parentLocation); int screenX = parentLocation[0] + x; int screenY = parentLocation[1] + y; - return screenX >= viewLocation[0] && screenX < viewLocation[0] + quickCamera.getWidth() && - screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight(); + return screenX >= viewLocation[0] && screenX < viewLocation[0] + cameraView.getWidth() && + screenY >= viewLocation[1] && screenY < viewLocation[1] + cameraView.getHeight(); } private int computeCameraTopPosition(int slideOffset) { @@ -469,7 +469,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { return getPaddingTop(); } - final int baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2; + final int baseCameraTop = (cameraView.getMeasuredHeight() - halfExpandedHeight) / 2; final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop; final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight), 0f, @@ -502,12 +502,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { public void onPause() { paused = true; - quickCamera.onPause(); + cameraView.onPause(); } public void onResume() { paused = false; - if (drawerState.isVisible()) quickCamera.onResume(); + if (drawerState.isVisible()) cameraView.onResume(); } public enum DrawerState { @@ -522,18 +522,18 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView { @Override public void onClick(View v) { boolean crop = drawerState != DrawerState.FULL_EXPANDED; - int imageHeight = crop ? getContainer().getKeyboardHeight() : quickCamera.getMeasuredHeight(); - Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight); - quickCamera.takePicture(previewRect); + int imageHeight = crop ? getContainer().getKeyboardHeight() : cameraView.getMeasuredHeight(); + Rect previewRect = new Rect(0, 0, cameraView.getMeasuredWidth(), imageHeight); + cameraView.takePicture(previewRect); } } private class CameraFlipClickListener implements OnClickListener { @Override public void onClick(View v) { - quickCamera.swapCamera(); - swapCameraButton.setImageResource(quickCamera.isRearCamera() ? R.drawable.quick_camera_front - : R.drawable.quick_camera_rear); + cameraView.flipCamera(); + swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front + : R.drawable.quick_camera_rear); } } diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java b/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java deleted file mode 100644 index 4f4dde6470..0000000000 --- a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.thoughtcrime.securesms.components.camera; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Rect; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; -import android.os.AsyncTask; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.support.annotation.NonNull; -import android.util.AttributeSet; -import android.util.Log; - -import com.commonsware.cwac.camera.CameraHost.FailureReason; -import com.commonsware.cwac.camera.SimpleCameraHost; - -import org.thoughtcrime.securesms.util.BitmapUtil; - -import java.io.IOException; -import java.util.List; - -@SuppressWarnings("deprecation") public class QuickCamera extends CameraView { - private static final String TAG = QuickCamera.class.getSimpleName(); - - private QuickCameraListener listener; - private boolean capturing; - private boolean started; - private QuickCameraHost cameraHost; - - public QuickCamera(Context context) { - this(context, null); - } - - public QuickCamera(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public QuickCamera(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - cameraHost = new QuickCameraHost(context); - setHost(cameraHost); - } - - @Override - public void onResume() { - if (started) return; - super.onResume(); - started = true; - } - - @Override - public void onPause() { - if (!started) return; - super.onPause(); - started = false; - } - - public boolean isStarted() { - return started; - } - - public void takePicture(final Rect previewRect) { - if (capturing) { - Log.w(TAG, "takePicture() called while previous capture pending."); - return; - } - - final Parameters cameraParameters = getCameraParameters(); - if (cameraParameters == null) { - Log.w(TAG, "camera not in capture-ready state"); - return; - } - - setOneShotPreviewCallback(new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(byte[] data, final Camera camera) { - final int rotation = getCameraPictureOrientation(); - final Size previewSize = cameraParameters.getPreviewSize(); - final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation); - - Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height); - Log.w(TAG, "previewFormat: " + cameraParameters.getPreviewFormat()); - Log.w(TAG, "croppingRect: " + croppingRect.toString()); - Log.w(TAG, "rotation: " + rotation); - new AsyncTask() { - @Override - protected byte[] doInBackground(byte[]... params) { - byte[] data = params[0]; - try { - - return BitmapUtil.createFromNV21(data, - previewSize.width, - previewSize.height, - rotation, - croppingRect); - } catch (IOException e) { - return null; - } - } - - @Override - protected void onPostExecute(byte[] imageBytes) { - capturing = false; - if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes); - } - }.execute(data); - } - }); - } - - private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) { - final int previewWidth = cameraPreviewSize.width; - final int previewHeight = cameraPreviewSize.height; - - if (rotation % 180 > 0) rotateRect(visibleRect); - - float scale = (float) previewWidth / visibleRect.width(); - if (visibleRect.height() * scale > previewHeight) { - scale = (float) previewHeight / visibleRect.height(); - } - final float newWidth = visibleRect.width() * scale; - final float newHeight = visibleRect.height() * scale; - final float centerX; - final float centerY = previewHeight / 2; - if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) { - centerX = previewWidth - newWidth / 2; - } else { - centerX = previewWidth / 2; - } - - visibleRect.set((int) (centerX - newWidth / 2), - (int) (centerY - newHeight / 2), - (int) (centerX + newWidth / 2), - (int) (centerY + newHeight / 2)); - - if (rotation % 180 > 0) rotateRect(visibleRect); - return visibleRect; - } - - @SuppressWarnings("SuspiciousNameCombination") - private void rotateRect(Rect rect) { - rect.set(rect.top, rect.left, rect.bottom, rect.right); - } - - public void setQuickCameraListener(QuickCameraListener listener) { - this.listener = listener; - } - - public boolean isMultipleCameras() { - return Camera.getNumberOfCameras() > 1; - } - - public boolean isRearCamera() { - return cameraHost.getCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK; - } - - public void swapCamera() { - cameraHost.swapCameraId(); - onPause(); - onResume(); - } - - public interface QuickCameraListener { - void onImageCapture(@NonNull final byte[] imageBytes); - void onCameraFail(FailureReason reason); - } - - private class QuickCameraHost extends SimpleCameraHost { - int cameraId = CameraInfo.CAMERA_FACING_BACK; - - public QuickCameraHost(Context context) { - super(context); - } - - @TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) @Override - public Parameters adjustPreviewParameters(Parameters parameters) { - List focusModes = parameters.getSupportedFocusModes(); - if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - } else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { - parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - } - return parameters; - } - - @Override - public int getCameraId() { - return cameraId; - } - - public void swapCameraId() { - if (isMultipleCameras()) { - if (cameraId == CameraInfo.CAMERA_FACING_BACK) cameraId = CameraInfo.CAMERA_FACING_FRONT; - else cameraId = CameraInfo.CAMERA_FACING_BACK; - } - } - - @Override - public void onCameraFail(FailureReason reason) { - super.onCameraFail(reason); - if (listener != null) listener.onCameraFail(reason); - } - } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java deleted file mode 100644 index 78e8a2769c..0000000000 --- a/src/org/thoughtcrime/securesms/components/camera/SurfacePreviewStrategy.java +++ /dev/null @@ -1,82 +0,0 @@ -/*** - Copyright (c) 2013 CommonsWare, LLC - - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -package org.thoughtcrime.securesms.components.camera; - -import android.hardware.Camera; -import android.media.MediaRecorder; -import android.util.Log; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; - -import java.io.IOException; - -class SurfacePreviewStrategy implements PreviewStrategy, - SurfaceHolder.Callback { - private final static String TAG = SurfacePreviewStrategy.class.getSimpleName(); - private final CameraView cameraView; - private SurfaceView preview=null; - private SurfaceHolder previewHolder=null; - private boolean ready = false; - - @SuppressWarnings("deprecation") - SurfacePreviewStrategy(CameraView cameraView) { - this.cameraView=cameraView; - preview=new SurfaceView(cameraView.getContext()); - previewHolder=preview.getHolder(); - previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - previewHolder.addCallback(this); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.w(TAG, "surfaceCreated()"); - ready = true; - synchronized (cameraView) { cameraView.notifyAll(); } - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, - int width, int height) { - Log.w(TAG, "surfaceChanged()"); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.w(TAG, "surfaceDestroyed()"); - cameraView.onPause(); - } - - @Override - public void attach(Camera camera) throws IOException { - Log.w(TAG, "attach(Camera)"); - camera.setPreviewDisplay(previewHolder); - } - - @Override - public void attach(MediaRecorder recorder) { - recorder.setPreviewDisplay(previewHolder.getSurface()); - } - - @Override - public View getWidget() { - return(preview); - } - - @Override - public boolean isReady() { - return ready; - } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java b/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java deleted file mode 100644 index 4b7049eeb2..0000000000 --- a/src/org/thoughtcrime/securesms/components/camera/TexturePreviewStrategy.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.thoughtcrime.securesms.components.camera; -/*** - Copyright (c) 2013 CommonsWare, LLC - - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import android.annotation.TargetApi; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.media.MediaRecorder; -import android.os.Build; -import android.util.Log; -import android.view.TextureView; -import android.view.View; - -import java.io.IOException; - -@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) -class TexturePreviewStrategy implements PreviewStrategy, - TextureView.SurfaceTextureListener { - private final static String TAG = TexturePreviewStrategy.class.getSimpleName(); - private final CameraView cameraView; - private TextureView widget=null; - private SurfaceTexture surface=null; - - TexturePreviewStrategy(CameraView cameraView) { - this.cameraView=cameraView; - widget=new TextureView(cameraView.getContext()); - widget.setSurfaceTextureListener(this); - } - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, - int width, int height) { - Log.w(TAG, "onSurfaceTextureAvailable()"); - this.surface=surface; - synchronized (cameraView) { cameraView.notifyAll(); } - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, - int width, int height) { - Log.w(TAG, "onSurfaceTextureChanged()"); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - Log.w(TAG, "onSurfaceTextureDestroyed()"); - cameraView.onPause(); - - return(true); - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - // no-op - } - - @Override - public void attach(Camera camera) throws IOException { - camera.setPreviewTexture(surface); - } - - @Override - public void attach(MediaRecorder recorder) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // no-op - } - else { - throw new IllegalStateException( - "Cannot use TextureView with MediaRecorder"); - } - } - - @Override - public boolean isReady() { - return widget.isAvailable(); - } - - @Override - public View getWidget() { - return(widget); - } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index 962cf65f6e..7fa5d2ed96 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -166,14 +166,24 @@ public class BitmapUtil { return bytes; } + /* + * NV21 a.k.a. YUV420sp + * YUV 4:2:0 planar image, with 8 bit Y samples, followed by interleaved V/U plane with 8bit 2x2 + * subsampled chroma samples. + * + * http://www.fourcc.org/yuv.php#NV21 + */ public static byte[] rotateNV21(@NonNull final byte[] yuv, final int width, final int height, final int rotation) + throws IOException { if (rotation == 0) return yuv; if (rotation % 90 != 0 || rotation < 0 || rotation > 270) { throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0"); + } else if ((width * height * 3) / 2 != yuv.length) { + throw new IOException("provided width and height don't jive with the data length"); } final byte[] output = new byte[yuv.length]; diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 6d5377888e..e1d3bc08e0 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -55,6 +55,7 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -330,11 +331,33 @@ public class Util { } } - public static void runOnMain(Runnable runnable) { + public static void runOnMain(final @NonNull Runnable runnable) { if (isMainThread()) runnable.run(); else handler.post(runnable); } + public static void runOnMainSync(final @NonNull Runnable runnable) { + if (isMainThread()) { + runnable.run(); + } else { + final CountDownLatch sync = new CountDownLatch(1); + runOnMain(new Runnable() { + @Override public void run() { + try { + runnable.run(); + } finally { + sync.countDown(); + } + } + }); + try { + sync.await(); + } catch (InterruptedException ie) { + throw new AssertionError(ie); + } + } + } + public static boolean equals(@Nullable Object a, @Nullable Object b) { return a == b || (a != null && a.equals(b)); }