mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-24 23:17:21 +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
|
maven { // textdrawable
|
||||||
url 'https://dl.bintray.com/amulyakhare/maven'
|
url 'https://dl.bintray.com/amulyakhare/maven'
|
||||||
}
|
}
|
||||||
maven { // cwac-camera
|
|
||||||
url 'https://repo.commonsware.com.s3.amazonaws.com'
|
|
||||||
}
|
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
}
|
}
|
||||||
@ -72,7 +69,6 @@ dependencies {
|
|||||||
exclude group: 'com.android.support', module: 'support-v4'
|
exclude group: 'com.android.support', module: 'support-v4'
|
||||||
}
|
}
|
||||||
compile 'com.madgag.spongycastle:prov:1.51.0.0'
|
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'
|
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
|
||||||
|
|
||||||
compile 'org.whispersystems:jobmanager:1.0.2'
|
compile 'org.whispersystems:jobmanager:1.0.2'
|
||||||
@ -126,7 +122,6 @@ dependencyVerification {
|
|||||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
||||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
||||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
||||||
'com.commonsware.cwac:camera:dcc93ddbb2f0393114fa1f31a13fe9e6edfcf5dbe96b22bc4b66c7b15e179054',
|
|
||||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||||
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
|
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
|
||||||
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
|
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
<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:id="@+id/quick_camera"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:background="#00000000"
|
android:background="#00000000"
|
||||||
android:src="@drawable/quick_camera_front"
|
android:src="@drawable/quick_camera_rear"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:background="#00000000"
|
android:background="#00000000"
|
||||||
android:src="@drawable/quick_camera_front"
|
android:src="@drawable/quick_camera_rear"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
|
@ -55,7 +55,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.AlertDialogWrapper;
|
import com.afollestad.materialdialogs.AlertDialogWrapper;
|
||||||
import com.commonsware.cwac.camera.CameraHost.FailureReason;
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
import org.thoughtcrime.redphone.RedPhone;
|
import org.thoughtcrime.redphone.RedPhone;
|
||||||
@ -1344,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCameraFail(FailureReason reason) {
|
public void onCameraFail() {
|
||||||
Toast.makeText(this, R.string.ConversationActivity_quick_camera_unavailable, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.ConversationActivity_quick_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||||
quickAttachmentDrawer.hide(false);
|
quickAttachmentDrawer.hide(false);
|
||||||
quickAttachmentToggle.disable();
|
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.Context;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.hardware.Camera;
|
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;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.OrientationEventListener;
|
import android.view.OrientationEventListener;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.List;
|
||||||
|
|
||||||
import com.commonsware.cwac.camera.CameraHost;
|
|
||||||
import com.commonsware.cwac.camera.CameraHost.FailureReason;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.jobqueue.Job;
|
import org.whispersystems.jobqueue.Job;
|
||||||
import org.whispersystems.jobqueue.JobParameters;
|
import org.whispersystems.jobqueue.JobParameters;
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class CameraView extends FrameLayout {
|
public class CameraView extends FrameLayout {
|
||||||
private static final String TAG = CameraView.class.getSimpleName();
|
private static final String TAG = CameraView.class.getSimpleName();
|
||||||
|
|
||||||
private PreviewStrategy previewStrategy = null;
|
private final CameraSurfaceView surface;
|
||||||
private Camera.Size previewSize = null;
|
private final OnOrientationChange onOrientationChange;
|
||||||
private volatile Camera camera = null;
|
|
||||||
private boolean inPreview = false;
|
private @NonNull volatile Optional<Camera> camera = Optional.absent();
|
||||||
private boolean cameraReady = false;
|
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
||||||
private CameraHost host = null;
|
|
||||||
private OnOrientationChange onOrientationChange = null;
|
private boolean started;
|
||||||
|
private @Nullable CameraViewListener listener;
|
||||||
private int displayOrientation = -1;
|
private int displayOrientation = -1;
|
||||||
private int outputOrientation = -1;
|
private int outputOrientation = -1;
|
||||||
private int cameraId = -1;
|
|
||||||
private int lastPictureOrientation = -1;
|
|
||||||
|
|
||||||
public CameraView(Context context) {
|
public CameraView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@ -71,51 +74,41 @@ public class CameraView extends FrameLayout {
|
|||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
setBackgroundColor(Color.BLACK);
|
setBackgroundColor(Color.BLACK);
|
||||||
|
|
||||||
|
if (isMultiCamera()) cameraId = CameraInfo.CAMERA_FACING_FRONT;
|
||||||
|
|
||||||
|
surface = new CameraSurfaceView(getContext());
|
||||||
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
|
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
|
||||||
}
|
addView(surface);
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
|
if (started) return;
|
||||||
|
started = true;
|
||||||
Log.w(TAG, "onResume() queued");
|
Log.w(TAG, "onResume() queued");
|
||||||
final CameraHost host = getHost();
|
enqueueTask(new SerialAsyncTask<Camera>() {
|
||||||
submitTask(new SerializedAsyncTask<FailureReason>() {
|
@Override
|
||||||
@Override protected FailureReason onRunBackground() {
|
protected @Nullable Camera onRunBackground() {
|
||||||
try {
|
try {
|
||||||
cameraId = host.getCameraId();
|
|
||||||
if (cameraId >= 0) {
|
if (cameraId >= 0) {
|
||||||
camera = Camera.open(cameraId);
|
return Camera.open(cameraId);
|
||||||
} else {
|
} else {
|
||||||
return FailureReason.NO_CAMERAS_REPORTED;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return FailureReason.UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override protected void onPostMain(FailureReason result) {
|
@Override
|
||||||
if (result != null) {
|
protected void onPostMain(@Nullable Camera camera) {
|
||||||
host.onCameraFail(result);
|
if (camera == null) {
|
||||||
|
if (listener != null) listener.onCameraFail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraView.this.camera = Optional.of(camera);
|
||||||
try {
|
try {
|
||||||
cameraReady = true;
|
|
||||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||||
onOrientationChange.enable();
|
onOrientationChange.enable();
|
||||||
}
|
}
|
||||||
@ -123,111 +116,87 @@ public class CameraView extends FrameLayout {
|
|||||||
synchronized (CameraView.this) {
|
synchronized (CameraView.this) {
|
||||||
CameraView.this.notifyAll();
|
CameraView.this.notifyAll();
|
||||||
}
|
}
|
||||||
previewCreated();
|
onCameraReady();
|
||||||
initPreview();
|
|
||||||
requestLayout();
|
requestLayout();
|
||||||
invalidate();
|
invalidate();
|
||||||
Log.w(TAG, "onResume() completed");
|
Log.w(TAG, "onResume() completed");
|
||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException e) {
|
||||||
Log.w(TAG, "exception when starting camera preview", re);
|
Log.w(TAG, "exception when starting camera preview", e);
|
||||||
try {
|
onPause();
|
||||||
previewDestroyed();
|
|
||||||
} catch (RuntimeException re2) {
|
|
||||||
Log.w(TAG, "also failed to release camera", re2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
|
if (!started) return;
|
||||||
|
started = false;
|
||||||
Log.w(TAG, "onPause() queued");
|
Log.w(TAG, "onPause() queued");
|
||||||
submitTask(new SerializedAsyncTask<Void>() {
|
final Optional<Camera> cameraToDestroy = camera;
|
||||||
|
|
||||||
|
enqueueTask(new SerialAsyncTask<Void>() {
|
||||||
@Override protected void onPreMain() {
|
@Override protected void onPreMain() {
|
||||||
cameraReady = false;
|
camera = Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected Void onRunBackground() {
|
@Override protected Void onRunBackground() {
|
||||||
previewDestroyed();
|
if (cameraToDestroy.isPresent()) {
|
||||||
|
stopPreview();
|
||||||
|
cameraToDestroy.get().release();
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void onPostMain(Void avoid) {
|
@Override protected void onPostMain(Void avoid) {
|
||||||
onOrientationChange.disable();
|
onOrientationChange.disable();
|
||||||
previewSize = null;
|
|
||||||
displayOrientation = -1;
|
displayOrientation = -1;
|
||||||
outputOrientation = -1;
|
outputOrientation = -1;
|
||||||
cameraId = -1;
|
|
||||||
lastPictureOrientation = -1;
|
|
||||||
Log.w(TAG, "onPause() completed");
|
Log.w(TAG, "onPause() completed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on CameraPreview.java from ApiDemos
|
public boolean isStarted() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
|
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera.isPresent()) {
|
||||||
|
final Size preferredPreviewSize = CameraUtils.getPreferredPreviewSize(displayOrientation,
|
||||||
|
getMeasuredWidth(),
|
||||||
|
getMeasuredHeight(),
|
||||||
|
camera.get());
|
||||||
|
final Parameters parameters = camera.get().getParameters();
|
||||||
|
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
|
||||||
|
stopPreview();
|
||||||
|
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
|
||||||
|
camera.get().setParameters(parameters);
|
||||||
|
requestLayout();
|
||||||
|
startPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
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 width = r - l;
|
||||||
final int height = b - t;
|
final int height = b - t;
|
||||||
final int previewWidth;
|
final int previewWidth;
|
||||||
final int previewHeight;
|
final int previewHeight;
|
||||||
|
|
||||||
// handle orientation
|
if (camera.isPresent()) {
|
||||||
|
final Size previewSize = camera.get().getParameters().getPreviewSize();
|
||||||
if (previewSize != null && (getDisplayOrientation() == 90 || getDisplayOrientation() == 270)) {
|
if (displayOrientation == 90 || displayOrientation == 270) {
|
||||||
previewWidth = previewSize.height;
|
previewWidth = previewSize.height;
|
||||||
previewHeight = previewSize.width;
|
previewHeight = previewSize.width;
|
||||||
} else if (previewSize != null) {
|
} else {
|
||||||
previewWidth = previewSize.width;
|
previewWidth = previewSize.width;
|
||||||
previewHeight = previewSize.height;
|
previewHeight = previewSize.height;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
previewWidth = width;
|
previewWidth = width;
|
||||||
previewHeight = height;
|
previewHeight = height;
|
||||||
@ -237,113 +206,92 @@ public class CameraView extends FrameLayout {
|
|||||||
Log.w(TAG, "skipping layout due to zero-width/height preview size");
|
Log.w(TAG, "skipping layout due to zero-width/height preview size");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight);
|
||||||
|
|
||||||
boolean useFirstStrategy = (width * previewHeight > height * previewWidth);
|
if (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;
|
final int scaledChildHeight = previewHeight * width / previewWidth;
|
||||||
child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
|
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() {
|
public void setListener(@Nullable CameraViewListener listener) {
|
||||||
return displayOrientation;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOneShotPreviewCallback(PreviewCallback callback) {
|
public boolean isMultiCamera() {
|
||||||
if (camera != null) camera.setOneShotPreviewCallback(callback);
|
return Camera.getNumberOfCameras() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Camera.Parameters getCameraParameters() {
|
public boolean isRearCamera() {
|
||||||
return camera == null || !cameraReady ? null : camera.getParameters();
|
return cameraId == CameraInfo.CAMERA_FACING_BACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void previewCreated() {
|
public void flipCamera() {
|
||||||
Log.w(TAG, "previewCreated() queued");
|
if (Camera.getNumberOfCameras() > 1) {
|
||||||
final CameraHost host = getHost();
|
cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
|
||||||
submitTask(new PostInitializationTask<Void>() {
|
? 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) {
|
@Override protected void onPostMain(Void avoid) {
|
||||||
|
if (camera.isPresent()) {
|
||||||
try {
|
try {
|
||||||
if (camera != null) {
|
camera.get().setPreviewDisplay(surface.getHolder());
|
||||||
previewStrategy.attach(camera);
|
|
||||||
}
|
|
||||||
} 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();
|
startPreview();
|
||||||
requestLayout();
|
} catch (IOException e) {
|
||||||
invalidate();
|
Log.w(TAG, e);
|
||||||
Log.w(TAG, "initPreview() completed");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPreview() {
|
private void startPreview() {
|
||||||
camera.startPreview();
|
if (camera.isPresent()) {
|
||||||
inPreview = true;
|
camera.get().startPreview();
|
||||||
getHost().autoFocusAvailable();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopPreview() {
|
private void stopPreview() {
|
||||||
camera.startPreview();
|
if (camera.isPresent()) {
|
||||||
inPreview = false;
|
camera.get().stopPreview();
|
||||||
getHost().autoFocusUnavailable();
|
}
|
||||||
camera.stopPreview();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on
|
// based on
|
||||||
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
|
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
|
||||||
// and http://stackoverflow.com/a/10383164/115145
|
// and http://stackoverflow.com/a/10383164/115145
|
||||||
private void setCameraDisplayOrientation() {
|
private void setCameraDisplayOrientation() {
|
||||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
Camera.CameraInfo info = getCameraInfo();
|
||||||
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
|
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
|
||||||
int degrees = 0;
|
int degrees = 0;
|
||||||
DisplayMetrics dm = new DisplayMetrics();
|
DisplayMetrics dm = new DisplayMetrics();
|
||||||
|
|
||||||
Camera.getCameraInfo(cameraId, info);
|
|
||||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||||
|
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
@ -361,64 +309,51 @@ public class CameraView extends FrameLayout {
|
|||||||
displayOrientation = (info.orientation - degrees + 360) % 360;
|
displayOrientation = (info.orientation - degrees + 360) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean wasInPreview = inPreview;
|
|
||||||
|
|
||||||
if (inPreview) {
|
|
||||||
stopPreview();
|
stopPreview();
|
||||||
}
|
camera.get().setDisplayOrientation(displayOrientation);
|
||||||
|
|
||||||
camera.setDisplayOrientation(displayOrientation);
|
|
||||||
|
|
||||||
if (wasInPreview) {
|
|
||||||
startPreview();
|
startPreview();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public int getCameraPictureOrientation() {
|
public int getCameraPictureOrientation() {
|
||||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
|
||||||
|
|
||||||
Camera.getCameraInfo(cameraId, info);
|
|
||||||
|
|
||||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||||
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
|
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
|
||||||
.getDefaultDisplay()
|
.getDefaultDisplay()
|
||||||
.getOrientation());
|
.getOrientation());
|
||||||
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
} else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
|
||||||
outputOrientation = (360 - displayOrientation) % 360;
|
outputOrientation = (360 - displayOrientation) % 360;
|
||||||
} else {
|
} else {
|
||||||
outputOrientation = displayOrientation;
|
outputOrientation = displayOrientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPictureOrientation != outputOrientation) {
|
|
||||||
lastPictureOrientation = outputOrientation;
|
|
||||||
}
|
|
||||||
return outputOrientation;
|
return outputOrientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on:
|
private @NonNull CameraInfo getCameraInfo() {
|
||||||
// http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setRotation(int)
|
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) {
|
public int getCameraPictureRotation(int orientation) {
|
||||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
final CameraInfo info = getCameraInfo();
|
||||||
Camera.getCameraInfo(cameraId, info);
|
final int rotation;
|
||||||
int rotation;
|
|
||||||
|
|
||||||
orientation = (orientation + 45) / 90 * 90;
|
orientation = (orientation + 45) / 90 * 90;
|
||||||
|
|
||||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||||
rotation = (info.orientation - orientation + 360) % 360;
|
rotation = (info.orientation - orientation + 360) % 360;
|
||||||
}
|
} else {
|
||||||
else { // back-facing camera
|
|
||||||
rotation = (info.orientation + orientation) % 360;
|
rotation = (info.orientation + orientation) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rotation;
|
return rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
Activity getActivity() {
|
|
||||||
return (Activity)getContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnOrientationChange extends OrientationEventListener {
|
private class OnOrientationChange extends OrientationEventListener {
|
||||||
public OnOrientationChange(Context context) {
|
public OnOrientationChange(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -427,19 +362,18 @@ public class CameraView extends FrameLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOrientationChanged(int orientation) {
|
public void onOrientationChanged(int orientation) {
|
||||||
if (camera != null && orientation != ORIENTATION_UNKNOWN) {
|
if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) {
|
||||||
int newOutputOrientation = getCameraPictureRotation(orientation);
|
int newOutputOrientation = getCameraPictureRotation(orientation);
|
||||||
|
|
||||||
if (newOutputOrientation != outputOrientation) {
|
if (newOutputOrientation != outputOrientation) {
|
||||||
outputOrientation = newOutputOrientation;
|
outputOrientation = newOutputOrientation;
|
||||||
|
|
||||||
Camera.Parameters params = camera.getParameters();
|
Camera.Parameters params = camera.get().getParameters();
|
||||||
|
|
||||||
params.setRotation(outputOrientation);
|
params.setRotation(outputOrientation);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
camera.setParameters(params);
|
camera.get().setParameters(params);
|
||||||
lastPictureOrientation = outputOrientation;
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Log.e(TAG, "Exception updating camera parameters in orientation change", 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);
|
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());
|
super(JobParameters.newBuilder().withGroupId(CameraView.class.getSimpleName()).create());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +451,7 @@ public class CameraView extends FrameLayout {
|
|||||||
@Override public final void onRun() {
|
@Override public final void onRun() {
|
||||||
try {
|
try {
|
||||||
onWait();
|
onWait();
|
||||||
runOnMainSync(new Runnable() {
|
Util.runOnMainSync(new Runnable() {
|
||||||
@Override public void run() {
|
@Override public void run() {
|
||||||
onPreMain();
|
onPreMain();
|
||||||
}
|
}
|
||||||
@ -472,7 +459,7 @@ public class CameraView extends FrameLayout {
|
|||||||
|
|
||||||
final Result result = onRunBackground();
|
final Result result = onRunBackground();
|
||||||
|
|
||||||
runOnMainSync(new Runnable() {
|
Util.runOnMainSync(new Runnable() {
|
||||||
@Override public void run() {
|
@Override public void run() {
|
||||||
onPostMain(result);
|
onPostMain(result);
|
||||||
}
|
}
|
||||||
@ -488,44 +475,61 @@ public class CameraView extends FrameLayout {
|
|||||||
|
|
||||||
@Override public void onCanceled() { }
|
@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 onWait() throws PreconditionsNotMetException {}
|
||||||
protected void onPreMain() {}
|
protected void onPreMain() {}
|
||||||
protected Result onRunBackground() { return null; }
|
protected Result onRunBackground() { return null; }
|
||||||
protected void onPostMain(Result result) {}
|
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 {
|
@Override protected void onWait() throws PreconditionsNotMetException {
|
||||||
synchronized (CameraView.this) {
|
synchronized (CameraView.this) {
|
||||||
if (!cameraReady) {
|
if (!camera.isPresent()) {
|
||||||
throw new PreconditionsNotMetException();
|
throw new PreconditionsNotMetException();
|
||||||
}
|
}
|
||||||
while (camera == null || previewSize == null || !previewStrategy.isReady()) {
|
while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
|
||||||
Log.w(TAG, String.format("waiting. camera? %s previewSize? %s prevewStrategy? %s",
|
Log.w(TAG, String.format("waiting. surface ready? %s", surface.isReady()));
|
||||||
camera != null, previewSize != null, previewStrategy.isReady()));
|
|
||||||
Util.wait(CameraView.this, 0);
|
Util.wait(CameraView.this, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PreconditionsNotMetException extends Exception {}
|
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.R;
|
||||||
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
||||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
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.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
@ -36,7 +36,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
|
|
||||||
private final ViewDragHelper dragHelper;
|
private final ViewDragHelper dragHelper;
|
||||||
|
|
||||||
private QuickCamera quickCamera;
|
private CameraView cameraView;
|
||||||
private int coverViewPosition;
|
private int coverViewPosition;
|
||||||
private KeyboardAwareLinearLayout container;
|
private KeyboardAwareLinearLayout container;
|
||||||
private View coverView;
|
private View coverView;
|
||||||
@ -74,12 +74,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
|
|
||||||
private void initializeView() {
|
private void initializeView() {
|
||||||
inflate(getContext(), R.layout.quick_attachment_drawer, this);
|
inflate(getContext(), R.layout.quick_attachment_drawer, this);
|
||||||
quickCamera = (QuickCamera) findViewById(R.id.quick_camera);
|
cameraView = ViewUtil.findById(this, R.id.quick_camera);
|
||||||
updateControlsView();
|
updateControlsView();
|
||||||
|
|
||||||
coverViewPosition = getChildCount();
|
coverViewPosition = getChildCount();
|
||||||
controls.setVisibility(GONE);
|
controls.setVisibility(GONE);
|
||||||
quickCamera.setVisibility(GONE);
|
cameraView.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDeviceSupported(Context context) {
|
public static boolean isDeviceSupported(Context context) {
|
||||||
@ -108,7 +108,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
if (rotationChanged) {
|
if (rotationChanged) {
|
||||||
if (isShowing()) {
|
if (isShowing()) {
|
||||||
quickCamera.onPause();
|
cameraView.onPause();
|
||||||
}
|
}
|
||||||
updateControlsView();
|
updateControlsView();
|
||||||
setDrawerStateAndUpdate(drawerState, true);
|
setDrawerStateAndUpdate(drawerState, true);
|
||||||
@ -123,13 +123,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
shutterButton = (ImageButton) controls.findViewById(R.id.shutter_button);
|
shutterButton = (ImageButton) controls.findViewById(R.id.shutter_button);
|
||||||
swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
|
swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
|
||||||
fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
|
fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
|
||||||
if (quickCamera.isMultipleCameras()) {
|
if (cameraView.isMultiCamera()) {
|
||||||
swapCameraButton.setVisibility(View.VISIBLE);
|
swapCameraButton.setVisibility(View.VISIBLE);
|
||||||
swapCameraButton.setOnClickListener(new CameraFlipClickListener());
|
swapCameraButton.setOnClickListener(new CameraFlipClickListener());
|
||||||
}
|
}
|
||||||
shutterButton.setOnClickListener(new ShutterClickListener());
|
shutterButton.setOnClickListener(new ShutterClickListener());
|
||||||
fullScreenButton.setOnClickListener(new FullscreenClickListener());
|
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;
|
this.controls = controls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +170,11 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
int childLeft = paddingLeft;
|
int childLeft = paddingLeft;
|
||||||
int childBottom;
|
int childBottom;
|
||||||
|
|
||||||
if (child == quickCamera) {
|
if (child == cameraView) {
|
||||||
childTop = computeCameraTopPosition(slideOffset);
|
childTop = computeCameraTopPosition(slideOffset);
|
||||||
childBottom = childTop + childHeight;
|
childBottom = childTop + childHeight;
|
||||||
if (quickCamera.getMeasuredWidth() < getMeasuredWidth())
|
if (cameraView.getMeasuredWidth() < getMeasuredWidth())
|
||||||
childLeft = (getMeasuredWidth() - quickCamera.getMeasuredWidth()) / 2 + paddingLeft;
|
childLeft = (getMeasuredWidth() - cameraView.getMeasuredWidth()) / 2 + paddingLeft;
|
||||||
} else if (child == controls) {
|
} else if (child == controls) {
|
||||||
childBottom = getMeasuredHeight();
|
childBottom = getMeasuredHeight();
|
||||||
} else {
|
} else {
|
||||||
@ -271,14 +271,14 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
ViewCompat.postInvalidateOnAnimation(this);
|
ViewCompat.postInvalidateOnAnimation(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slideOffset == 0 && quickCamera.isStarted()) {
|
if (slideOffset == 0 && cameraView.isStarted()) {
|
||||||
quickCamera.onPause();
|
cameraView.onPause();
|
||||||
controls.setVisibility(GONE);
|
controls.setVisibility(GONE);
|
||||||
quickCamera.setVisibility(GONE);
|
cameraView.setVisibility(GONE);
|
||||||
} else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) {
|
} else if (slideOffset != 0 && !cameraView.isStarted() & !paused) {
|
||||||
controls.setVisibility(VISIBLE);
|
controls.setVisibility(VISIBLE);
|
||||||
quickCamera.setVisibility(VISIBLE);
|
cameraView.setVisibility(VISIBLE);
|
||||||
quickCamera.onResume();
|
cameraView.onResume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,10 +335,10 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
|
|
||||||
public void setListener(AttachmentDrawerListener listener) {
|
public void setListener(AttachmentDrawerListener listener) {
|
||||||
this.listener = 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);
|
void onAttachmentDrawerStateChanged(DrawerState drawerState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,8 +391,8 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
int slideOffset = getTargetSlideOffset();
|
int slideOffset = getTargetSlideOffset();
|
||||||
dragHelper.captureChildView(coverView, 0);
|
dragHelper.captureChildView(coverView, 0);
|
||||||
dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
|
dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
|
||||||
dragHelper.captureChildView(quickCamera, 0);
|
dragHelper.captureChildView(cameraView, 0);
|
||||||
dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
|
dragHelper.settleCapturedViewAt(cameraView.getLeft(), computeCameraTopPosition(slideOffset));
|
||||||
ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
|
ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -455,13 +455,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
@SuppressWarnings("ResourceType")
|
@SuppressWarnings("ResourceType")
|
||||||
private boolean isDragViewUnder(int x, int y) {
|
private boolean isDragViewUnder(int x, int y) {
|
||||||
int[] viewLocation = new int[2];
|
int[] viewLocation = new int[2];
|
||||||
quickCamera.getLocationOnScreen(viewLocation);
|
cameraView.getLocationOnScreen(viewLocation);
|
||||||
int[] parentLocation = new int[2];
|
int[] parentLocation = new int[2];
|
||||||
this.getLocationOnScreen(parentLocation);
|
this.getLocationOnScreen(parentLocation);
|
||||||
int screenX = parentLocation[0] + x;
|
int screenX = parentLocation[0] + x;
|
||||||
int screenY = parentLocation[1] + y;
|
int screenY = parentLocation[1] + y;
|
||||||
return screenX >= viewLocation[0] && screenX < viewLocation[0] + quickCamera.getWidth() &&
|
return screenX >= viewLocation[0] && screenX < viewLocation[0] + cameraView.getWidth() &&
|
||||||
screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight();
|
screenY >= viewLocation[1] && screenY < viewLocation[1] + cameraView.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int computeCameraTopPosition(int slideOffset) {
|
private int computeCameraTopPosition(int slideOffset) {
|
||||||
@ -469,7 +469,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
return getPaddingTop();
|
return getPaddingTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
final int baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2;
|
final int baseCameraTop = (cameraView.getMeasuredHeight() - halfExpandedHeight) / 2;
|
||||||
final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop;
|
final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop;
|
||||||
final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
|
final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
|
||||||
0f,
|
0f,
|
||||||
@ -502,12 +502,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
|
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
paused = true;
|
paused = true;
|
||||||
quickCamera.onPause();
|
cameraView.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
paused = false;
|
paused = false;
|
||||||
if (drawerState.isVisible()) quickCamera.onResume();
|
if (drawerState.isVisible()) cameraView.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DrawerState {
|
public enum DrawerState {
|
||||||
@ -522,17 +522,17 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
boolean crop = drawerState != DrawerState.FULL_EXPANDED;
|
boolean crop = drawerState != DrawerState.FULL_EXPANDED;
|
||||||
int imageHeight = crop ? getContainer().getKeyboardHeight() : quickCamera.getMeasuredHeight();
|
int imageHeight = crop ? getContainer().getKeyboardHeight() : cameraView.getMeasuredHeight();
|
||||||
Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight);
|
Rect previewRect = new Rect(0, 0, cameraView.getMeasuredWidth(), imageHeight);
|
||||||
quickCamera.takePicture(previewRect);
|
cameraView.takePicture(previewRect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CameraFlipClickListener implements OnClickListener {
|
private class CameraFlipClickListener implements OnClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
quickCamera.swapCamera();
|
cameraView.flipCamera();
|
||||||
swapCameraButton.setImageResource(quickCamera.isRearCamera() ? R.drawable.quick_camera_front
|
swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front
|
||||||
: R.drawable.quick_camera_rear);
|
: 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;
|
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,
|
public static byte[] rotateNV21(@NonNull final byte[] yuv,
|
||||||
final int width,
|
final int width,
|
||||||
final int height,
|
final int height,
|
||||||
final int rotation)
|
final int rotation)
|
||||||
|
throws IOException
|
||||||
{
|
{
|
||||||
if (rotation == 0) return yuv;
|
if (rotation == 0) return yuv;
|
||||||
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
|
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
|
||||||
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
|
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];
|
final byte[] output = new byte[yuv.length];
|
||||||
|
@ -55,6 +55,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
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();
|
if (isMainThread()) runnable.run();
|
||||||
else handler.post(runnable);
|
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) {
|
public static boolean equals(@Nullable Object a, @Nullable Object b) {
|
||||||
return a == b || (a != null && a.equals(b));
|
return a == b || (a != null && a.equals(b));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user