Implement BQP Android UI using QR codes

This commit is contained in:
str4d
2016-02-14 05:41:36 +00:00
parent 701cfdba48
commit 8cacc73bef
14 changed files with 1322 additions and 3 deletions

View File

@@ -0,0 +1,258 @@
package org.briarproject.android.util;
import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
import static android.hardware.Camera.Parameters.FOCUS_MODE_EDOF;
import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
import static android.view.SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation")
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
AutoFocusCallback {
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
private static final Logger LOG =
Logger.getLogger(CameraView.class.getName());
private Camera camera = null;
private PreviewConsumer previewConsumer = null;
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
private boolean autoFocus = false, surfaceExists = false;
public CameraView(Context context) {
super(context);
initialize();
}
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
private void initialize() {
setKeepScreenOn(true);
SurfaceHolder holder = getHolder();
if (Build.VERSION.SDK_INT < 11)
holder.setType(SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(this);
}
public void start(Camera camera, PreviewConsumer previewConsumer,
int rotationDegrees) {
this.camera = camera;
this.previewConsumer = previewConsumer;
setDisplayOrientation(rotationDegrees);
Parameters params = camera.getParameters();
setFocusMode(params);
setPreviewSize(params);
applyParameters(params);
if (surfaceExists) startPreview(getHolder());
}
public void stop() {
stopPreview();
try {
camera.release();
} catch (RuntimeException e) {
LOG.log(WARNING, "Error releasing camera", e);
}
camera = null;
}
private void startPreview(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
if (autoFocus) camera.autoFocus(this);
previewConsumer.start(camera);
} catch (IOException e) {
LOG.log(WARNING, "Error starting camera preview", e);
}
}
private void stopPreview() {
try {
previewConsumer.stop();
if (autoFocus) camera.cancelAutoFocus();
camera.stopPreview();
} catch (RuntimeException e) {
LOG.log(WARNING, "Error stopping camera preview", e);
}
}
private void setDisplayOrientation(int rotationDegrees) {
int orientation;
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(0, info);
if (info.facing == CAMERA_FACING_FRONT) {
orientation = (info.orientation + rotationDegrees) % 360;
orientation = (360 - orientation) % 360;
} else {
orientation = (info.orientation - rotationDegrees + 360) % 360;
}
if(LOG.isLoggable(INFO))
LOG.info("Display orientation " + orientation + " degrees");
camera.setDisplayOrientation(orientation);
displayOrientation = orientation;
}
private void setFocusMode(Parameters params) {
if (Build.VERSION.SDK_INT >= 15 &&
params.isVideoStabilizationSupported()) {
LOG.info("Enabling video stabilisation");
params.setVideoStabilization(true);
}
// This returns null on the HTC Wildfire S
List<String> sceneModes = params.getSupportedSceneModes();
if (sceneModes == null) sceneModes = Collections.emptyList();
List<String> focusModes = params.getSupportedFocusModes();
if (LOG.isLoggable(INFO)) {
LOG.info("Scene modes: " + sceneModes);
LOG.info("Focus modes: " + focusModes);
}
if (sceneModes.contains(SCENE_MODE_BARCODE)) {
LOG.info("Setting scene mode to barcode");
params.setSceneMode(SCENE_MODE_BARCODE);
} else if (Build.VERSION.SDK_INT >= 14 &&
focusModes.contains(FOCUS_MODE_CONTINUOUS_PICTURE)) {
LOG.info("Setting focus mode to continuous picture");
params.setFocusMode(FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(FOCUS_MODE_CONTINUOUS_VIDEO)) {
LOG.info("Setting focus mode to continuous video");
params.setFocusMode(FOCUS_MODE_CONTINUOUS_VIDEO);
} else if (focusModes.contains(FOCUS_MODE_EDOF)) {
LOG.info("Setting focus mode to EDOF");
params.setFocusMode(FOCUS_MODE_EDOF);
} else if (focusModes.contains(FOCUS_MODE_MACRO)) {
LOG.info("Setting focus mode to macro");
params.setFocusMode(FOCUS_MODE_MACRO);
autoFocus = true;
} else if (focusModes.contains(FOCUS_MODE_AUTO)) {
LOG.info("Setting focus mode to auto");
params.setFocusMode(FOCUS_MODE_AUTO);
autoFocus = true;
} else if (focusModes.contains(FOCUS_MODE_FIXED)) {
LOG.info("Setting focus mode to fixed");
params.setFocusMode(FOCUS_MODE_FIXED);
} else {
LOG.info("No suitable focus mode");
}
params.setZoom(0);
}
private void setPreviewSize(Parameters params) {
if (surfaceWidth == 0 || surfaceHeight == 0) return;
float idealRatio = (float) surfaceWidth / surfaceHeight;
DisplayMetrics screen = getContext().getResources().getDisplayMetrics();
int screenMax = Math.max(screen.widthPixels, screen.heightPixels);
boolean rotatePreview = displayOrientation % 180 == 90;
List<Size> sizes = params.getSupportedPreviewSizes();
Size bestSize = null;
float bestScore = 0;
for (Size size : sizes) {
int width = rotatePreview ? size.height : size.width;
int height = rotatePreview ? size.width : size.height;
float ratio = (float) width / height;
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
int pixels = width * height;
float score = width * height / stretch;
if (LOG.isLoggable(INFO)) {
LOG.info("Size " + size.width + "x" + size.height
+ ", stretch " + stretch + ", pixels " + pixels
+ ", score " + score);
}
// Large preview sizes can crash older devices
int maxDimension = Math.max(width, height);
if (Build.VERSION.SDK_INT < 14 && maxDimension > screenMax) {
LOG.info("Too large for screen");
continue;
}
if (bestSize == null || score > bestScore) {
bestSize = size;
bestScore = score;
}
}
if (bestSize != null) {
if(LOG.isLoggable(INFO))
LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
params.setPreviewSize(bestSize.width, bestSize.height);
}
}
private void applyParameters(Parameters params) {
try {
camera.setParameters(params);
} catch (RuntimeException e) {
LOG.log(WARNING, "Error setting camera parameters", e);
}
}
public void surfaceCreated(SurfaceHolder holder) {
LOG.info("Surface created");
surfaceExists = true;
if (camera != null) startPreview(holder);
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (LOG.isLoggable(INFO)) LOG.info("Surface changed: " + w + "x" + h);
surfaceWidth = w;
surfaceHeight = h;
if (camera == null) return; // We are stopped
stopPreview();
Parameters params = camera.getParameters();
setPreviewSize(params);
applyParameters(params);
startPreview(holder);
}
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.info("Surface destroyed");
surfaceExists = false;
holder.removeCallback(this);
}
public void onAutoFocus(boolean success, final Camera camera) {
LOG.info("Auto focus succeeded: " + success);
postDelayed(new Runnable() {
public void run() {
retryAutoFocus();
}
}, AUTO_FOCUS_RETRY_DELAY);
}
private void retryAutoFocus() {
try {
if (camera != null) camera.autoFocus(this);
} catch (RuntimeException e) {
LOG.log(WARNING, "Error retrying auto focus", e);
}
}
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.android.util;
import android.hardware.Camera;
@SuppressWarnings("deprecation")
public interface PreviewConsumer {
void start(Camera camera);
void stop();
}

View File

@@ -0,0 +1,100 @@
package org.briarproject.android.util;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
@SuppressWarnings("deprecation")
public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private static final Logger LOG =
Logger.getLogger(QrCodeDecoder.class.getName());
private final Reader reader = new QRCodeReader();
private final ResultCallback callback;
private boolean stopped = false;
public QrCodeDecoder(ResultCallback callback) {
this.callback = callback;
}
public void start(Camera camera) {
stopped = false;
askForPreviewFrame(camera);
}
public void stop() {
stopped = true;
}
private void askForPreviewFrame(Camera camera) {
if (!stopped) camera.setOneShotPreviewCallback(this);
}
public void onPreviewFrame(byte[] data, Camera camera) {
if (!stopped) {
Size size = camera.getParameters().getPreviewSize();
new DecoderTask(camera, data, size.width, size.height).execute();
}
}
private class DecoderTask extends AsyncTask<Void, Void, Void> {
final Camera camera;
final byte[] data;
final int width, height;
DecoderTask(Camera camera, byte[] data, int width, int height) {
this.camera = camera;
this.data = data;
this.width = width;
this.height = height;
}
@Override
protected Void doInBackground(Void... params) {
long now = System.currentTimeMillis();
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
height, 0, 0, width, height, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
Result result = null;
try {
result = reader.decode(bitmap);
} catch (ReaderException e) {
return null; // No barcode found
} finally {
reader.reset();
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Decoding barcode took " + duration + " ms");
callback.handleResult(result);
return null;
}
@Override
protected void onPostExecute(Void result) {
askForPreviewFrame(camera);
}
}
public interface ResultCallback {
void handleResult(Result result);
}
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.android.util;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.DisplayMetrics;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
public class QrCodeUtils {
private static final Logger LOG =
Logger.getLogger(QrCodeUtils.class.getName());
public static Bitmap createQrCode(Activity activity, String input) {
// Get narrowest screen dimension
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
try {
// Generate QR code
final BitMatrix encoded = new QRCodeWriter().encode(
input, BarcodeFormat.QR_CODE, smallestDimen, smallestDimen);
// Convert QR code to Bitmap
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) ? Color.BLACK : Color.WHITE;
}
}
Bitmap qr = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
qr.setPixels(pixels, 0, width, 0, 0, width, height);
return qr;
} catch (WriterException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return null;
}
}
}