mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
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:
parent
08be47c03e
commit
8fd0ea39aa
@ -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',
|
||||
|
@ -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"
|
||||
|
@ -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" />
|
||||
|
@ -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"/>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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];
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user