mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
Merge branch '346-qr-code-optimisations' into 'maintenance-0.16'
Backport: Improve QR code scanning on phones with high res cameras and slow CPUs See merge request akwizgran/briar!715
This commit is contained in:
@@ -6,7 +6,6 @@ import android.hardware.Camera.AutoFocusCallback;
|
|||||||
import android.hardware.Camera.CameraInfo;
|
import android.hardware.Camera.CameraInfo;
|
||||||
import android.hardware.Camera.Parameters;
|
import android.hardware.Camera.Parameters;
|
||||||
import android.hardware.Camera.Size;
|
import android.hardware.Camera.Size;
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
@@ -22,6 +21,7 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
|
||||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||||
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
|
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
|
||||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
|
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
|
||||||
@@ -32,6 +32,7 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
|
|||||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@@ -41,7 +42,12 @@ import static java.util.logging.Level.WARNING;
|
|||||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||||
AutoFocusCallback, View.OnClickListener {
|
AutoFocusCallback, View.OnClickListener {
|
||||||
|
|
||||||
|
// Heuristic for the ideal preview size - small previews don't have enough
|
||||||
|
// detail, large previews are slow to decode
|
||||||
|
private static final int IDEAL_PIXELS = 500 * 1000;
|
||||||
|
|
||||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CameraView.class.getName());
|
Logger.getLogger(CameraView.class.getName());
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
|
private int cameraIndex = 0;
|
||||||
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;
|
||||||
@@ -89,15 +96,32 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public void start() throws CameraException {
|
public void start(int rotationDegrees) throws CameraException {
|
||||||
LOG.info("Opening camera");
|
LOG.info("Opening camera");
|
||||||
try {
|
try {
|
||||||
camera = Camera.open();
|
int cameras = Camera.getNumberOfCameras();
|
||||||
|
if (cameras == 0) throw new CameraException("No camera");
|
||||||
|
// Try to find a back-facing camera
|
||||||
|
for (int i = 0; i < cameras; i++) {
|
||||||
|
CameraInfo info = new CameraInfo();
|
||||||
|
Camera.getCameraInfo(i, info);
|
||||||
|
if (info.facing == CAMERA_FACING_BACK) {
|
||||||
|
LOG.info("Using back-facing camera");
|
||||||
|
camera = Camera.open(i);
|
||||||
|
cameraIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we can't find a back-facing camera, use a front-facing one
|
||||||
|
if (camera == null) {
|
||||||
|
LOG.info("Using front-facing camera");
|
||||||
|
camera = Camera.open(0);
|
||||||
|
cameraIndex = 0;
|
||||||
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
if (camera == null) throw new CameraException("No back-facing camera");
|
setDisplayOrientation(rotationDegrees);
|
||||||
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);
|
||||||
@@ -163,7 +187,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
private void startConsumer() throws CameraException {
|
private void startConsumer() throws CameraException {
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
startAutoFocus();
|
startAutoFocus();
|
||||||
previewConsumer.start(camera);
|
previewConsumer.start(camera, cameraIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -199,13 +223,17 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link Camera#setDisplayOrientation(int)}.
|
||||||
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
private void setDisplayOrientation(int rotationDegrees)
|
private void setDisplayOrientation(int rotationDegrees)
|
||||||
throws CameraException {
|
throws CameraException {
|
||||||
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
int orientation;
|
int orientation;
|
||||||
CameraInfo info = new CameraInfo();
|
CameraInfo info = new CameraInfo();
|
||||||
try {
|
try {
|
||||||
Camera.getCameraInfo(0, info);
|
Camera.getCameraInfo(cameraIndex, info);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
@@ -215,9 +243,11 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
} else {
|
} else {
|
||||||
orientation = (info.orientation - rotationDegrees + 360) % 360;
|
orientation = (info.orientation - rotationDegrees + 360) % 360;
|
||||||
}
|
}
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Display orientation " + orientation + " degrees");
|
LOG.info("Screen rotation " + rotationDegrees
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
+ " degrees, camera orientation " + orientation
|
||||||
|
+ " degrees");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
camera.setDisplayOrientation(orientation);
|
camera.setDisplayOrientation(orientation);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@@ -285,8 +315,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void setVideoStabilisation(Parameters params) {
|
private void setVideoStabilisation(Parameters params) {
|
||||||
if (Build.VERSION.SDK_INT >= 15 &&
|
if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
|
||||||
params.isVideoStabilizationSupported()) {
|
|
||||||
params.setVideoStabilization(true);
|
params.setVideoStabilization(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,6 +342,8 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void setPreviewSize(Parameters params) {
|
private void setPreviewSize(Parameters params) {
|
||||||
if (surfaceWidth == 0 || surfaceHeight == 0) return;
|
if (surfaceWidth == 0 || surfaceHeight == 0) return;
|
||||||
|
// Choose a preview size that's close to the aspect ratio of the
|
||||||
|
// surface and close to the ideal size for decoding
|
||||||
float idealRatio = (float) surfaceWidth / surfaceHeight;
|
float idealRatio = (float) surfaceWidth / surfaceHeight;
|
||||||
boolean rotatePreview = displayOrientation % 180 == 90;
|
boolean rotatePreview = displayOrientation % 180 == 90;
|
||||||
List<Size> sizes = params.getSupportedPreviewSizes();
|
List<Size> sizes = params.getSupportedPreviewSizes();
|
||||||
@@ -323,11 +354,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
int height = rotatePreview ? size.width : size.height;
|
int height = rotatePreview ? size.width : size.height;
|
||||||
float ratio = (float) width / height;
|
float ratio = (float) width / height;
|
||||||
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
|
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
|
||||||
int pixels = width * height;
|
float pixels = width * height;
|
||||||
float score = pixels / stretch;
|
float zoom = Math.max(pixels / IDEAL_PIXELS, IDEAL_PIXELS / pixels);
|
||||||
|
float score = 1 / (stretch * zoom);
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Size " + size.width + "x" + size.height
|
LOG.info("Size " + size.width + "x" + size.height
|
||||||
+ ", stretch " + stretch + ", pixels " + pixels
|
+ ", stretch " + stretch + ", zoom " + zoom
|
||||||
+ ", score " + score);
|
+ ", score " + score);
|
||||||
}
|
}
|
||||||
if (bestSize == null || score > bestScore) {
|
if (bestSize == null || score > bestScore) {
|
||||||
@@ -358,7 +390,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 15) {
|
if (SDK_INT >= 15) {
|
||||||
LOG.info("Video stabilisation enabled: "
|
LOG.info("Video stabilisation enabled: "
|
||||||
+ params.getVideoStabilization());
|
+ params.getVideoStabilization());
|
||||||
}
|
}
|
||||||
@@ -389,8 +421,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
surface.release();
|
surface.release();
|
||||||
}
|
}
|
||||||
surface = holder.getSurface();
|
surface = holder.getSurface();
|
||||||
// Start the preview when the camera and the surface are both ready
|
// We'll start the preview when surfaceChanged() is called
|
||||||
if (camera != null && !previewStarted) startPreview(holder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -416,7 +447,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
surfaceWidth = w;
|
surfaceWidth = w;
|
||||||
surfaceHeight = h;
|
surfaceHeight = h;
|
||||||
if (camera == null) return; // We are stopped
|
if (camera == null) return; // We are stopped
|
||||||
stopPreview();
|
if (previewStarted) stopPreview();
|
||||||
try {
|
try {
|
||||||
Parameters params = camera.getParameters();
|
Parameters params = camera.getParameters();
|
||||||
setPreviewSize(params);
|
setPreviewSize(params);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
interface PreviewConsumer {
|
interface PreviewConsumer {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void start(Camera camera);
|
void start(Camera camera, int cameraIndex);
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void stop();
|
void stop();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
|
import android.hardware.Camera.CameraInfo;
|
||||||
import android.hardware.Camera.PreviewCallback;
|
import android.hardware.Camera.PreviewCallback;
|
||||||
import android.hardware.Camera.Size;
|
import android.hardware.Camera.Size;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
@@ -36,20 +37,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
private final ResultCallback callback;
|
private final ResultCallback callback;
|
||||||
|
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
|
private int cameraIndex = 0;
|
||||||
|
|
||||||
QrCodeDecoder(ResultCallback callback) {
|
QrCodeDecoder(ResultCallback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Camera camera) {
|
public void start(Camera camera, int cameraIndex) {
|
||||||
this.camera = camera;
|
this.camera = camera;
|
||||||
|
this.cameraIndex = cameraIndex;
|
||||||
askForPreviewFrame();
|
askForPreviewFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
camera = null;
|
camera = null;
|
||||||
|
cameraIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -61,45 +65,64 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||||
if (camera == this.camera) {
|
if (camera == this.camera) {
|
||||||
|
LOG.info("Got preview frame");
|
||||||
try {
|
try {
|
||||||
Size size = camera.getParameters().getPreviewSize();
|
Size size = camera.getParameters().getPreviewSize();
|
||||||
new DecoderTask(data, size.width, size.height).execute();
|
// The preview should be in NV21 format: width * height bytes of
|
||||||
|
// Y followed by width * height / 2 bytes of interleaved U and V
|
||||||
|
if (data.length == size.width * size.height * 3 / 2) {
|
||||||
|
CameraInfo info = new CameraInfo();
|
||||||
|
Camera.getCameraInfo(cameraIndex, info);
|
||||||
|
new DecoderTask(data, size.width, size.height,
|
||||||
|
info.orientation).execute();
|
||||||
|
} else {
|
||||||
|
// Camera parameters have changed - ask for a new preview
|
||||||
|
LOG.info("Preview size does not match camera parameters");
|
||||||
|
askForPreviewFrame();
|
||||||
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.log(WARNING, "Error getting camera parameters.", e);
|
LOG.log(WARNING, "Error getting camera parameters.", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Camera has changed, ignoring preview frame");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
private final int width, height;
|
private final int width, height, orientation;
|
||||||
|
|
||||||
private DecoderTask(byte[] data, int width, int height) {
|
private DecoderTask(byte[] data, int width, int height,
|
||||||
|
int orientation) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
||||||
height, 0, 0, width, height, false);
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
|
||||||
Result result = null;
|
Result result = null;
|
||||||
try {
|
try {
|
||||||
result = reader.decode(bitmap);
|
result = reader.decode(bitmap);
|
||||||
|
long duration = System.currentTimeMillis() - now;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Decoding barcode took " + duration + " ms");
|
||||||
} catch (ReaderException e) {
|
} catch (ReaderException e) {
|
||||||
return null; // No barcode found
|
// No barcode found
|
||||||
|
long duration = System.currentTimeMillis() - now;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("No barcode found after " + duration + " ms");
|
||||||
|
return null;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return null; // Preview data did not match width and height
|
LOG.warning("Invalid preview frame");
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
reader.reset();
|
reader.reset();
|
||||||
}
|
}
|
||||||
long duration = System.currentTimeMillis() - now;
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Decoding barcode took " + duration + " ms");
|
|
||||||
callback.handleResult(result);
|
callback.handleResult(result);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -110,6 +133,19 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BinaryBitmap binarize(byte[] data, int width, int height,
|
||||||
|
int orientation) {
|
||||||
|
// Crop to a square at the top (portrait) or left (landscape) of the
|
||||||
|
// screen - this will be faster to decode and should include
|
||||||
|
// everything visible in the viewfinder
|
||||||
|
int crop = Math.min(width, height);
|
||||||
|
int left = orientation >= 180 ? width - crop : 0;
|
||||||
|
int top = orientation >= 180 ? height - crop : 0;
|
||||||
|
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||||
|
height, left, top, crop, crop, false);
|
||||||
|
return new BinaryBitmap(new HybridBinarizer(src));
|
||||||
|
}
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ResultCallback {
|
interface ResultCallback {
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,24 @@ class QrCodeUtils {
|
|||||||
// Generate QR code
|
// Generate QR code
|
||||||
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
||||||
smallestDimen, smallestDimen);
|
smallestDimen, smallestDimen);
|
||||||
// Convert QR code to Bitmap
|
return renderQrCode(encoded);
|
||||||
int width = encoded.getWidth();
|
|
||||||
int height = encoded.getHeight();
|
|
||||||
int[] pixels = new int[width * height];
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
pixels[y * width + x] = encoded.get(x, y) ? BLACK : WHITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
|
||||||
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
|
||||||
return qr;
|
|
||||||
} catch (WriterException e) {
|
} catch (WriterException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Bitmap renderQrCode(BitMatrix matrix) {
|
||||||
|
int width = matrix.getWidth();
|
||||||
|
int height = matrix.getHeight();
|
||||||
|
int[] pixels = new int[width * height];
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
pixels[y * width + x] = matrix.get(x, y) ? BLACK : WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
||||||
|
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||||
|
return qr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ 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.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.Display;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
@@ -144,7 +147,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
try {
|
try {
|
||||||
cameraView.start();
|
cameraView.start(getScreenRotationDegrees());
|
||||||
} catch (CameraException e) {
|
} catch (CameraException e) {
|
||||||
logCameraExceptionAndFinish(e);
|
logCameraExceptionAndFinish(e);
|
||||||
}
|
}
|
||||||
@@ -164,6 +167,25 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link Camera#setDisplayOrientation(int)}.
|
||||||
|
*/
|
||||||
|
private int getScreenRotationDegrees() {
|
||||||
|
Display d = getActivity().getWindowManager().getDefaultDisplay();
|
||||||
|
switch (d.getRotation()) {
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
return 0;
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
return 90;
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
return 180;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
return 270;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|||||||
@@ -1,79 +1,89 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<android.support.constraint.ConstraintLayout
|
||||||
android:id="@+id/imageView"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:padding="@dimen/margin_large">
|
||||||
android:layout_weight="1"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/qr_code_intro"/>
|
|
||||||
|
|
||||||
<ScrollView
|
<ImageView
|
||||||
android:id="@+id/scrollView"
|
android:id="@+id/diagram"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:padding="@dimen/margin_medium"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/qr_code_intro"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/explanationText"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
|
||||||
|
|
||||||
<LinearLayout
|
<ImageView
|
||||||
|
android:id="@+id/explanationImage"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:paddingTop="@dimen/margin_large"
|
||||||
|
android:paddingLeft="@dimen/margin_large"
|
||||||
|
android:paddingRight="@dimen/margin_large"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/qr_code_explanation"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/explanationText"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/explanationText"
|
||||||
|
style="@style/BriarTextBody"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:padding="@dimen/margin_large"
|
||||||
|
android:text="@string/face_to_face"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/explanationImage"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/explanationBorder"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@drawable/border_explanation"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/explanationImage"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
|
||||||
|
|
||||||
|
<android.support.constraint.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="diagram,explanationBorder"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/continueButton"
|
||||||
|
style="@style/BriarButton.Default"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_gravity="center_horizontal"
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
android:text="@string/continue_button"
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
</android.support.constraint.ConstraintLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/border_explanation"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/margin_large">
|
|
||||||
|
|
||||||
<ImageView
|
</ScrollView>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:src="@drawable/qr_code_explanation"
|
|
||||||
tools:ignore="ContentDescription"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:text="@string/face_to_face"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/continueButton"
|
|
||||||
style="@style/BriarButton.Default"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:text="@string/continue_button"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<org.briarproject.briar.android.keyagreement.CameraView
|
||||||
|
android:id="@+id/camera_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/camera_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/status_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/background_light"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/margin_medium"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/connect_status"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="@dimen/margin_large"
|
||||||
|
tools:text="Connection failed"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/qr_code"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/container_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_above="@+id/title_progress_bar"
|
||||||
|
android:layout_centerHorizontal="true"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_progress_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="@dimen/margin_large"
|
||||||
|
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -2,56 +2,62 @@
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
android:id="@+id/scrollView"
|
android:id="@+id/scrollView"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<android.support.constraint.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:padding="@dimen/margin_large">
|
||||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
|
||||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
|
||||||
android:paddingTop="@dimen/margin_activity_vertical">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/diagram"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
|
android:paddingBottom="@dimen/margin_large"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/qr_code_intro"
|
android:src="@drawable/qr_code_intro"
|
||||||
tools:ignore="ContentDescription"/>
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/explanationImage"/>
|
||||||
|
|
||||||
<LinearLayout
|
<ImageView
|
||||||
|
android:id="@+id/explanationImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:paddingTop="@dimen/margin_large"
|
||||||
|
android:paddingLeft="@dimen/margin_large"
|
||||||
|
android:paddingRight="@dimen/margin_large"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/qr_code_explanation"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/diagram"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/explanationText"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/explanationText"
|
||||||
|
style="@style/BriarTextBody"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/margin_small"
|
android:padding="@dimen/margin_large"
|
||||||
android:layout_marginRight="@dimen/margin_small"
|
android:text="@string/face_to_face"
|
||||||
android:layout_marginTop="@dimen/margin_xlarge"
|
app:layout_constraintTop_toBottomOf="@id/explanationImage"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/continueButton"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/explanationBorder"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
android:background="@drawable/border_explanation"
|
android:background="@drawable/border_explanation"
|
||||||
android:orientation="vertical"
|
app:layout_constraintTop_toTopOf="@id/explanationImage"
|
||||||
android:padding="@dimen/margin_large">
|
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||||
<ImageView
|
app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:padding="@dimen/margin_medium"
|
|
||||||
android:src="@drawable/qr_code_explanation"
|
|
||||||
android:contentDescription="@string/face_to_face"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/BriarTextBody"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:text="@string/face_to_face"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/continueButton"
|
android:id="@+id/continueButton"
|
||||||
@@ -60,8 +66,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
android:text="@string/continue_button"/>
|
android:text="@string/continue_button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/explanationText"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
Reference in New Issue
Block a user