Don't crash if camera is reopened or surface is recreated.

This commit is contained in:
akwizgran
2016-11-09 12:37:38 +00:00
parent 138a6e11a7
commit bb82bd70e2
3 changed files with 62 additions and 77 deletions

View File

@@ -6,7 +6,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.hardware.Camera;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -58,7 +57,6 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class ShowQrCodeFragment extends BaseEventFragment public class ShowQrCodeFragment extends BaseEventFragment
implements QrCodeDecoder.ResultCallback { implements QrCodeDecoder.ResultCallback {
@@ -86,7 +84,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
private ViewGroup mainProgressContainer; private ViewGroup mainProgressContainer;
private BluetoothStateReceiver receiver; private BluetoothStateReceiver receiver;
private QrCodeDecoder decoder;
private boolean gotRemotePayload, waitingForBluetooth; private boolean gotRemotePayload, waitingForBluetooth;
private KeyAgreementTask task; private KeyAgreementTask task;
@@ -136,8 +133,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
decoder = new QrCodeDecoder(this);
} }
@Override @Override
@@ -163,8 +159,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} else { } else {
startListening(); startListening();
} }
cameraView.start();
openCamera();
} }
@Override @Override
@@ -172,7 +167,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
super.onStop(); super.onStop();
stopListening(); stopListening();
if (receiver != null) getActivity().unregisterReceiver(receiver); if (receiver != null) getActivity().unregisterReceiver(receiver);
releaseCamera(); cameraView.stop();
} }
@UiThread @UiThread
@@ -200,45 +195,11 @@ public class ShowQrCodeFragment extends BaseEventFragment
}); });
} }
@SuppressWarnings("deprecation")
@UiThread
private void openCamera() {
LOG.info("Opening camera");
Camera camera;
try {
camera = Camera.open();
} catch (RuntimeException e) {
LOG.log(WARNING, e.toString(), e);
camera = null;
}
if (camera == null) {
LOG.log(WARNING, "Error opening camera");
Toast.makeText(getActivity(), R.string.could_not_open_camera,
LENGTH_LONG).show();
finish();
return;
}
cameraView.start(camera, decoder, 0);
}
@UiThread
private void releaseCamera() {
LOG.info("Releasing camera");
try {
cameraView.stop();
} catch (RuntimeException e) {
LOG.log(WARNING, "Error releasing camera", e);
// TODO better solution
finish();
}
}
@UiThread @UiThread
private void reset() { private void reset() {
statusView.setVisibility(INVISIBLE); statusView.setVisibility(INVISIBLE);
cameraView.setVisibility(VISIBLE); cameraView.setVisibility(VISIBLE);
gotRemotePayload = false; gotRemotePayload = false;
cameraView.startConsumer();
startListening(); startListening();
} }
@@ -380,7 +341,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
LOG.info("Got result from decoder"); LOG.info("Got result from decoder");
if (!gotRemotePayload) { if (!gotRemotePayload) {
gotRemotePayload = true; gotRemotePayload = true;
cameraView.stopConsumer();
qrCodeScanned(result.getText()); qrCodeScanned(result.getText());
} }
} }

View File

@@ -28,7 +28,7 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final Reader reader = new QRCodeReader(); private final Reader reader = new QRCodeReader();
private final ResultCallback callback; private final ResultCallback callback;
private boolean stopped = false; private Camera camera = null;
public QrCodeDecoder(ResultCallback callback) { public QrCodeDecoder(ResultCallback callback) {
this.callback = callback; this.callback = callback;
@@ -36,37 +36,33 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
public void start(Camera camera) { public void start(Camera camera) {
stopped = false; this.camera = camera;
askForPreviewFrame(camera); askForPreviewFrame();
} }
@Override @Override
public void stop() { public void stop() {
stopped = true; camera = null;
} }
@UiThread @UiThread
private void askForPreviewFrame(Camera camera) { private void askForPreviewFrame() {
if (!stopped) camera.setOneShotPreviewCallback(this); if (camera != null) camera.setOneShotPreviewCallback(this);
} }
@UiThread @UiThread
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(byte[] data, Camera camera) {
if (!stopped) { Size size = camera.getParameters().getPreviewSize();
Size size = camera.getParameters().getPreviewSize(); new DecoderTask(data, size.width, size.height).execute();
new DecoderTask(camera, data, size.width, size.height).execute();
}
} }
private class DecoderTask extends AsyncTask<Void, Void, Void> { private class DecoderTask extends AsyncTask<Void, Void, Void> {
private final Camera camera;
private final byte[] data; private final byte[] data;
private final int width, height; private final int width, height;
DecoderTask(Camera camera, byte[] data, int width, int height) { DecoderTask(byte[] data, int width, int height) {
this.camera = camera;
this.data = data; this.data = data;
this.width = width; this.width = width;
this.height = height; this.height = height;
@@ -84,7 +80,7 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
} catch (ReaderException e) { } catch (ReaderException e) {
return null; // No barcode found return null; // No barcode found
} catch (RuntimeException e) { } catch (RuntimeException e) {
return null; // Decoding failed due to bug in decoder return null; // Preview data did not match width and height
} finally { } finally {
reader.reset(); reader.reset();
} }
@@ -97,7 +93,7 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
protected void onPostExecute(Void result) { protected void onPostExecute(Void result) {
askForPreviewFrame(camera); askForPreviewFrame();
} }
} }

View File

@@ -14,6 +14,8 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import org.briarproject.android.util.PreviewConsumer; import org.briarproject.android.util.PreviewConsumer;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@@ -33,6 +35,8 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CameraView extends SurfaceView implements SurfaceHolder.Callback, public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
AutoFocusCallback { AutoFocusCallback {
@@ -44,7 +48,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
private PreviewConsumer previewConsumer = null; private PreviewConsumer previewConsumer = null;
private Surface surface = null; private Surface surface = null;
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0; private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
private boolean autoFocus = false; private boolean previewStarted = false, autoFocus = false;
public CameraView(Context context) { public CameraView(Context context) {
super(context); super(context);
@@ -58,6 +62,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
@UiThread
public void setPreviewConsumer(PreviewConsumer previewConsumer) {
LOG.info("Setting preview consumer");
this.previewConsumer = previewConsumer;
}
@Override @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
@@ -70,15 +80,18 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
super.onDetachedFromWindow(); super.onDetachedFromWindow();
setKeepScreenOn(false); setKeepScreenOn(false);
getHolder().removeCallback(this); getHolder().removeCallback(this);
if (surface != null) surface.release();
} }
@UiThread @UiThread
public void start(Camera camera, PreviewConsumer previewConsumer, public void start() {
int rotationDegrees) { try {
this.camera = camera; LOG.info("Opening camera");
this.previewConsumer = previewConsumer; camera = Camera.open();
setDisplayOrientation(rotationDegrees); } catch (RuntimeException e) {
LOG.log(WARNING, "Error opening camera", e);
return;
}
setDisplayOrientation(0);
// Use barcode scene mode if it's available // Use barcode scene mode if it's available
Parameters params = camera.getParameters(); Parameters params = camera.getParameters();
params = setSceneMode(camera, params); params = setSceneMode(camera, params);
@@ -96,14 +109,17 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
enableAutoFocus(params.getFocusMode()); enableAutoFocus(params.getFocusMode());
// Log the parameters that are being used (maybe not what we asked for) // Log the parameters that are being used (maybe not what we asked for)
logCameraParameters(); logCameraParameters();
if (surface != null) startPreview(getHolder()); // Start the preview when the camera and the surface are both ready
if (surface != null && !previewStarted) startPreview(getHolder());
} }
@UiThread @UiThread
public void stop() { public void stop() {
if (camera == null) return;
stopPreview(); stopPreview();
try { try {
if (camera != null) camera.release(); LOG.info("Releasing camera");
camera.release();
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.log(WARNING, "Error releasing camera", e); LOG.log(WARNING, "Error releasing camera", e);
} }
@@ -112,9 +128,11 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
@UiThread @UiThread
private void startPreview(SurfaceHolder holder) { private void startPreview(SurfaceHolder holder) {
LOG.info("Starting preview");
try { try {
camera.setPreviewDisplay(holder); camera.setPreviewDisplay(holder);
camera.startPreview(); camera.startPreview();
previewStarted = true;
startConsumer(); startConsumer();
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
LOG.log(WARNING, "Error starting camera preview", e); LOG.log(WARNING, "Error starting camera preview", e);
@@ -123,24 +141,26 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
@UiThread @UiThread
private void stopPreview() { private void stopPreview() {
LOG.info("Stopping preview");
try { try {
stopConsumer(); stopConsumer();
if (camera != null) camera.stopPreview(); camera.stopPreview();
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.log(WARNING, "Error stopping camera preview", e); LOG.log(WARNING, "Error stopping camera preview", e);
} }
previewStarted = false;
} }
@UiThread @UiThread
public void startConsumer() { private void startConsumer() {
if (autoFocus) camera.autoFocus(this); if (autoFocus) camera.autoFocus(this);
previewConsumer.start(camera); previewConsumer.start(camera);
} }
@UiThread @UiThread
public void stopConsumer() { private void stopConsumer() {
if (previewConsumer != null) previewConsumer.stop();
if (autoFocus) camera.cancelAutoFocus(); if (autoFocus) camera.cancelAutoFocus();
previewConsumer.stop();
} }
@UiThread @UiThread
@@ -295,9 +315,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
@UiThread @UiThread
private void surfaceCreatedUi(SurfaceHolder holder) { private void surfaceCreatedUi(SurfaceHolder holder) {
LOG.info("Surface created"); LOG.info("Surface created");
if (surface != null) throw new IllegalStateException(); if (surface != null && surface != holder.getSurface()) {
LOG.info("Releasing old surface");
surface.release();
}
surface = holder.getSurface(); surface = holder.getSurface();
if (camera != null) startPreview(holder); // Start the preview when the camera and the surface are both ready
if (camera != null && !previewStarted) startPreview(holder);
} }
@Override @Override
@@ -314,9 +338,10 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
@UiThread @UiThread
private void surfaceChangedUi(SurfaceHolder holder, int w, int h) { private void surfaceChangedUi(SurfaceHolder holder, int w, int h) {
if (LOG.isLoggable(INFO)) LOG.info("Surface changed: " + w + "x" + h); if (LOG.isLoggable(INFO)) LOG.info("Surface changed: " + w + "x" + h);
// Release the previous surface if necessary if (surface != null && surface != holder.getSurface()) {
if (surface != null && surface != holder.getSurface()) LOG.info("Releasing old surface");
surface.release(); surface.release();
}
surface = holder.getSurface(); surface = holder.getSurface();
surfaceWidth = w; surfaceWidth = w;
surfaceHeight = h; surfaceHeight = h;
@@ -346,8 +371,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
@UiThread @UiThread
private void surfaceDestroyedUi(SurfaceHolder holder) { private void surfaceDestroyedUi(SurfaceHolder holder) {
LOG.info("Surface destroyed"); LOG.info("Surface destroyed");
if (holder.getSurface() != surface) throw new IllegalStateException(); if (surface != null && surface != holder.getSurface()) {
if (surface != null) surface.release(); LOG.info("Releasing old surface");
surface.release();
}
surface = null;
holder.getSurface().release();
} }
@Override @Override