Simplify excessively convoluted camera logic

1) QuickCamera logic moved into CameraView

2) The strategies for texture vs. surface view were too complex
   with no observed gain. Better to remove and have to re-add
   if necessary than assume it to be necessary.

3) Drop CWAC-Camera dependency - the device profiles weren't being
   used very much and even that is deprecated so we'd be left on
   our own with new hardware. Not worth it.

4) Selfies first.

5) Layout/orientation mathy logic from CWAC moved into CameraUtils,
   with the  hopes that most of it might be further simplified or
   rendered unnecessary in the future.

Closes #4326

// FREEBIE
This commit is contained in:
Jake McGinty 2015-10-26 17:20:09 -07:00 committed by Moxie Marlinspike
parent 08be47c03e
commit 8fd0ea39aa
15 changed files with 432 additions and 692 deletions

View File

@ -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',

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<org.thoughtcrime.securesms.components.camera.QuickCamera
<org.thoughtcrime.securesms.components.camera.CameraView
android:id="@+id/quick_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -31,7 +31,7 @@
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="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" />

View File

@ -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"/>

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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<Size> 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<Size> {
@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;
}
}
}

View File

@ -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> 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<FailureReason>() {
@Override protected FailureReason onRunBackground() {
enqueueTask(new SerialAsyncTask<Camera>() {
@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<Void>() {
final Optional<Camera> cameraToDestroy = camera;
enqueueTask(new SerialAsyncTask<Void>() {
@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<Void>() {
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<String> 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<Void>() {
@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<Void>() {
@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<Result> extends Job {
private static abstract class SerialAsyncTask<Result> 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<Result> extends SerializedAsyncTask<Result> {
private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
@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<byte[], Void, byte[]> {
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();
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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<byte[], Void, byte[]>() {
@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<String> 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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];

View File

@ -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));
}