mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
Implement BQP Android UI using QR codes
This commit is contained in:
258
briar-android/src/org/briarproject/android/util/CameraView.java
Normal file
258
briar-android/src/org/briarproject/android/util/CameraView.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public interface PreviewConsumer {
|
||||
|
||||
void start(Camera camera);
|
||||
|
||||
void stop();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user