mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
1 Commits
377_replac
...
278-bqp-ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e32139a89 |
@@ -10,6 +10,11 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<org.briarproject.android.util.ViewfinderView
|
||||||
|
android:id="@+id/viewfinder_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -43,4 +43,10 @@
|
|||||||
|
|
||||||
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
|
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
|
||||||
<color name="spinner_arrow">@color/briar_blue_dark</color>
|
<color name="spinner_arrow">@color/briar_blue_dark</color>
|
||||||
|
|
||||||
|
<!-- ViewfinderView -->
|
||||||
|
<color name="possible_result_points">#c0ffbd21</color> <!-- Material Yellow 700 with alpha -->
|
||||||
|
<color name="result_view">#b0000000</color>
|
||||||
|
<color name="viewfinder_laser">#d50000</color> <!-- Red accent 700 -->
|
||||||
|
<color name="viewfinder_mask">#60000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -19,6 +19,8 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.ResultPoint;
|
||||||
|
import com.google.zxing.ResultPointCallback;
|
||||||
|
|
||||||
import org.briarproject.R;
|
import org.briarproject.R;
|
||||||
import org.briarproject.android.AndroidComponent;
|
import org.briarproject.android.AndroidComponent;
|
||||||
@@ -27,6 +29,7 @@ import org.briarproject.android.fragment.BaseEventFragment;
|
|||||||
import org.briarproject.android.util.CameraView;
|
import org.briarproject.android.util.CameraView;
|
||||||
import org.briarproject.android.util.QrCodeDecoder;
|
import org.briarproject.android.util.QrCodeDecoder;
|
||||||
import org.briarproject.android.util.QrCodeUtils;
|
import org.briarproject.android.util.QrCodeUtils;
|
||||||
|
import org.briarproject.android.util.ViewfinderView;
|
||||||
import org.briarproject.api.event.Event;
|
import org.briarproject.api.event.Event;
|
||||||
import org.briarproject.api.event.KeyAgreementAbortedEvent;
|
import org.briarproject.api.event.KeyAgreementAbortedEvent;
|
||||||
import org.briarproject.api.event.KeyAgreementFailedEvent;
|
import org.briarproject.api.event.KeyAgreementFailedEvent;
|
||||||
@@ -55,7 +58,7 @@ import static java.util.logging.Level.WARNING;
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class ShowQrCodeFragment extends BaseEventFragment
|
public class ShowQrCodeFragment extends BaseEventFragment
|
||||||
implements QrCodeDecoder.ResultCallback {
|
implements QrCodeDecoder.ResultCallback, ResultPointCallback {
|
||||||
|
|
||||||
public static final String TAG = "ShowQrCodeFragment";
|
public static final String TAG = "ShowQrCodeFragment";
|
||||||
|
|
||||||
@@ -75,6 +78,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
protected Executor ioExecutor;
|
protected Executor ioExecutor;
|
||||||
|
|
||||||
private CameraView cameraView;
|
private CameraView cameraView;
|
||||||
|
private ViewfinderView viewfinderView;
|
||||||
private View statusView;
|
private View statusView;
|
||||||
private TextView status;
|
private TextView status;
|
||||||
private ImageView qrCode;
|
private ImageView qrCode;
|
||||||
@@ -109,9 +113,13 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
cameraView = (CameraView) view.findViewById(R.id.camera_view);
|
cameraView = (CameraView) view.findViewById(R.id.camera_view);
|
||||||
|
viewfinderView =
|
||||||
|
(ViewfinderView) view.findViewById(R.id.viewfinder_view);
|
||||||
statusView = view.findViewById(R.id.status_container);
|
statusView = view.findViewById(R.id.status_container);
|
||||||
status = (TextView) view.findViewById(R.id.connect_status);
|
status = (TextView) view.findViewById(R.id.connect_status);
|
||||||
qrCode = (ImageView) view.findViewById(R.id.qr_code);
|
qrCode = (ImageView) view.findViewById(R.id.qr_code);
|
||||||
|
|
||||||
|
viewfinderView.setFrameProvider(cameraView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -120,7 +128,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
|
|
||||||
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||||
|
|
||||||
decoder = new QrCodeDecoder(this);
|
decoder = new QrCodeDecoder(this, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -219,6 +227,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
} else {
|
} else {
|
||||||
cameraView.start(camera, decoder, 0);
|
cameraView.start(camera, decoder, 0);
|
||||||
|
viewfinderView.drawViewfinder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -355,6 +364,11 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void foundPossibleResultPoint(ResultPoint point) {
|
||||||
|
viewfinderView.addPossibleResultPoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.android.util;
|
package org.briarproject.android.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
import android.hardware.Camera.AutoFocusCallback;
|
import android.hardware.Camera.AutoFocusCallback;
|
||||||
import android.hardware.Camera.CameraInfo;
|
import android.hardware.Camera.CameraInfo;
|
||||||
@@ -13,6 +15,7 @@ import android.view.SurfaceHolder;
|
|||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -31,17 +34,25 @@ import static java.util.logging.Level.WARNING;
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||||
AutoFocusCallback {
|
AutoFocusCallback, ViewfinderView.FrameProvider {
|
||||||
|
|
||||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||||
|
private static final int MIN_FRAME_SIZE = 240;
|
||||||
|
private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CameraView.class.getName());
|
Logger.getLogger(CameraView.class.getName());
|
||||||
|
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
|
private Rect framingRect;
|
||||||
|
private Rect framingRectInPreview;
|
||||||
|
private Rect framingRectInSensor;
|
||||||
private PreviewConsumer previewConsumer = null;
|
private PreviewConsumer previewConsumer = null;
|
||||||
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
||||||
private boolean autoFocus = false, surfaceExists = false;
|
private boolean autoFocus = false, surfaceExists = false;
|
||||||
|
|
||||||
|
private Point cameraResolution;
|
||||||
|
private final Object cameraResolutionLock = new Object();
|
||||||
|
|
||||||
public CameraView(Context context) {
|
public CameraView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -184,6 +195,24 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
LOG.info("No suitable focus mode");
|
LOG.info("No suitable focus mode");
|
||||||
}
|
}
|
||||||
params.setZoom(0);
|
params.setZoom(0);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||||
|
List<Camera.Area> areas = new ArrayList<>();
|
||||||
|
areas.add(new Camera.Area(getFramingRectInSensor(), 1000));
|
||||||
|
if (params.getMaxNumFocusAreas() > 0) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Focus areas supported: " +
|
||||||
|
params.getMaxNumFocusAreas());
|
||||||
|
}
|
||||||
|
params.setFocusAreas(areas);
|
||||||
|
}
|
||||||
|
if (params.getMaxNumMeteringAreas() > 0) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Metering areas supported: " +
|
||||||
|
params.getMaxNumMeteringAreas());
|
||||||
|
}
|
||||||
|
params.setMeteringAreas(areas);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPreviewSize(Parameters params) {
|
private void setPreviewSize(Parameters params) {
|
||||||
@@ -222,6 +251,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
|
LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
|
||||||
params.setPreviewSize(bestSize.width, bestSize.height);
|
params.setPreviewSize(bestSize.width, bestSize.height);
|
||||||
|
synchronized (cameraResolutionLock) {
|
||||||
|
cameraResolution = new Point(bestSize.width, bestSize.height);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
synchronized (cameraResolutionLock) {
|
||||||
|
cameraResolution = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,4 +312,152 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
LOG.log(WARNING, "Error retrying auto focus", e);
|
LOG.log(WARNING, "Error retrying auto focus", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the framing rect which the UI should draw to show the user where to place the
|
||||||
|
* barcode. This target helps with alignment as well as forces the user to hold the device
|
||||||
|
* far enough away to ensure the image will be in focus.
|
||||||
|
*
|
||||||
|
* @return The rectangle to draw on screen in window coordinates.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Rect getFramingRect() {
|
||||||
|
if (framingRect == null) {
|
||||||
|
framingRect = calculateFramingRect(true);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Calculated framing rect: " + framingRect);
|
||||||
|
}
|
||||||
|
return framingRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the framing rect which the UI should draw to show the user where to place the
|
||||||
|
* barcode. This target helps with alignment as well as forces the user to hold the device
|
||||||
|
* far enough away to ensure the image will be in focus.
|
||||||
|
* <p/>
|
||||||
|
* Adapted from the Zxing Barcode Scanner.
|
||||||
|
*
|
||||||
|
* @return The rectangle to draw on screen in window coordinates.
|
||||||
|
*/
|
||||||
|
private Rect calculateFramingRect(boolean withOrientation) {
|
||||||
|
if (camera == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (surfaceWidth == 0 || surfaceHeight == 0) {
|
||||||
|
// Called early, before the surface is ready
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean portrait =
|
||||||
|
withOrientation && displayOrientation % 180 == 90;
|
||||||
|
int size = findDesiredDimensionInRange(
|
||||||
|
portrait ? surfaceWidth : surfaceHeight,
|
||||||
|
portrait ? surfaceHeight / 2 : surfaceWidth / 2,
|
||||||
|
MIN_FRAME_SIZE, MAX_FRAME_SIZE);
|
||||||
|
|
||||||
|
int leftOffset = portrait ?
|
||||||
|
(surfaceWidth - size) / 2 :
|
||||||
|
((surfaceWidth / 2) - size) / 2;
|
||||||
|
int topOffset = portrait ?
|
||||||
|
((surfaceHeight / 2) - size) / 2 :
|
||||||
|
(surfaceHeight - size) / 2;
|
||||||
|
return new Rect(leftOffset, topOffset, leftOffset + size,
|
||||||
|
topOffset + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the square that fits best inside the given region.
|
||||||
|
*/
|
||||||
|
private static int findDesiredDimensionInRange(int side1, int side2,
|
||||||
|
int hardMin, int hardMax) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Finding framing dimension, side1 = " + side1 +
|
||||||
|
", side2 = " + side2);
|
||||||
|
int minSide = Math.min(side1, side2);
|
||||||
|
int dim = 5 * minSide / 8; // Target 5/8 of smallest side
|
||||||
|
if (dim < hardMin) {
|
||||||
|
if (hardMin > minSide) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Returning minimum side length: " + minSide);
|
||||||
|
return minSide;
|
||||||
|
} else {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Returning hard minimum: " + hardMin);
|
||||||
|
return hardMin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dim > hardMax) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Returning hard maximum: " + hardMax);
|
||||||
|
return hardMax;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Returning desired dimension: " + dim);
|
||||||
|
return dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #getFramingRect} but coordinates are in terms of the preview
|
||||||
|
* frame, not UI / screen.
|
||||||
|
* <p/>
|
||||||
|
* Adapted from the Zxing Barcode Scanner.
|
||||||
|
*
|
||||||
|
* @return {@link Rect} expressing QR code scan area in terms of the preview size
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Rect getFramingRectInPreview() {
|
||||||
|
if (framingRectInPreview == null) {
|
||||||
|
Rect framingRect = getFramingRect();
|
||||||
|
if (framingRect == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Rect rect = new Rect(framingRect);
|
||||||
|
Point cameraResolution = getCameraResolution();
|
||||||
|
if (cameraResolution == null || surfaceWidth == 0 ||
|
||||||
|
surfaceHeight == 0) {
|
||||||
|
// Called early, before the surface is ready
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
rect.left = rect.left * cameraResolution.x / surfaceWidth;
|
||||||
|
rect.right = rect.right * cameraResolution.x / surfaceWidth;
|
||||||
|
rect.top = rect.top * cameraResolution.y / surfaceHeight;
|
||||||
|
rect.bottom = rect.bottom * cameraResolution.y / surfaceHeight;
|
||||||
|
framingRectInPreview = rect;
|
||||||
|
}
|
||||||
|
return framingRectInPreview;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point getCameraResolution() {
|
||||||
|
Point ret;
|
||||||
|
synchronized (cameraResolutionLock) {
|
||||||
|
ret = new Point(cameraResolution);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #getFramingRect} but coordinates are in terms of the sensor,
|
||||||
|
* not UI / screen (ie. it is independent of orientation)
|
||||||
|
*
|
||||||
|
* @return {@link Rect} expressing QR code scan area in terms of the sensor
|
||||||
|
*/
|
||||||
|
private Rect getFramingRectInSensor() {
|
||||||
|
if (framingRectInSensor == null) {
|
||||||
|
Rect framingRect = calculateFramingRect(false);
|
||||||
|
if (framingRect == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Rect rect = new Rect(framingRect);
|
||||||
|
if (surfaceWidth == 0 || surfaceHeight == 0) {
|
||||||
|
// Called early, before the surface is ready
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
rect.left = (rect.left * 2000 / surfaceWidth) - 1000;
|
||||||
|
rect.right = (rect.right * 2000 / surfaceWidth) - 1000;
|
||||||
|
rect.top = (rect.top * 2000 / surfaceHeight) - 1000;
|
||||||
|
rect.bottom = (rect.bottom * 2000 / surfaceHeight) - 1000;
|
||||||
|
framingRectInSensor = rect;
|
||||||
|
}
|
||||||
|
return framingRectInSensor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,18 @@ import android.hardware.Camera.Size;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.DecodeHintType;
|
||||||
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.LuminanceSource;
|
||||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
import com.google.zxing.Reader;
|
import com.google.zxing.Reader;
|
||||||
import com.google.zxing.ReaderException;
|
import com.google.zxing.ReaderException;
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.ResultPointCallback;
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
import com.google.zxing.qrcode.QRCodeReader;
|
import com.google.zxing.qrcode.QRCodeReader;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
@@ -26,11 +30,14 @@ 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 final ResultPointCallback pointCallback;
|
||||||
|
|
||||||
private boolean stopped = false;
|
private boolean stopped = false;
|
||||||
|
|
||||||
public QrCodeDecoder(ResultCallback callback) {
|
public QrCodeDecoder(ResultCallback callback,
|
||||||
|
ResultPointCallback pointCallback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.pointCallback = pointCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(Camera camera) {
|
public void start(Camera camera) {
|
||||||
@@ -72,9 +79,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||||
height, 0, 0, width, height, false);
|
height, 0, 0, width, height, false);
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
||||||
|
Map<DecodeHintType, Object> hints = new HashMap<>();
|
||||||
|
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
|
||||||
Result result = null;
|
Result result = null;
|
||||||
try {
|
try {
|
||||||
result = reader.decode(bitmap);
|
result = reader.decode(bitmap, hints);
|
||||||
} catch (ReaderException e) {
|
} catch (ReaderException e) {
|
||||||
return null; // No barcode found
|
return null; // No barcode found
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2008 ZXing authors
|
||||||
|
* Copyright (C) 2016 Sublime Software Ltd
|
||||||
|
*
|
||||||
|
* 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.briarproject.android.util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.zxing.ResultPoint;
|
||||||
|
|
||||||
|
import org.briarproject.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This view is overlaid on top of the camera preview. It adds the viewfinder
|
||||||
|
* rectangle and partial transparency outside it, as well as the laser scanner
|
||||||
|
* animation and result points.
|
||||||
|
*
|
||||||
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
|
*/
|
||||||
|
public final class ViewfinderView extends View {
|
||||||
|
|
||||||
|
private static final int[] SCANNER_ALPHA =
|
||||||
|
{0, 64, 128, 192, 255, 192, 128, 64};
|
||||||
|
private static final long ANIMATION_DELAY = 80L;
|
||||||
|
private static final int CURRENT_POINT_OPACITY = 0xA0;
|
||||||
|
private static final int MAX_RESULT_POINTS = 20;
|
||||||
|
private static final int POINT_SIZE = 6;
|
||||||
|
|
||||||
|
private FrameProvider frameProvider;
|
||||||
|
private final Paint paint;
|
||||||
|
private Bitmap resultBitmap;
|
||||||
|
private final int maskColor;
|
||||||
|
private final int resultColor;
|
||||||
|
private final int laserColor;
|
||||||
|
private final int resultPointColor;
|
||||||
|
private int scannerAlpha;
|
||||||
|
private List<ResultPoint> possibleResultPoints;
|
||||||
|
private List<ResultPoint> lastPossibleResultPoints;
|
||||||
|
|
||||||
|
// This constructor is used when the class is built from an XML resource.
|
||||||
|
public ViewfinderView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
if (isInEditMode()) {
|
||||||
|
paint = null;
|
||||||
|
maskColor = 0;
|
||||||
|
resultColor = 0;
|
||||||
|
laserColor = 0;
|
||||||
|
resultPointColor = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize these once for performance rather than calling them every
|
||||||
|
// time in onDraw().
|
||||||
|
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
Resources resources = getResources();
|
||||||
|
maskColor = resources.getColor(R.color.viewfinder_mask);
|
||||||
|
resultColor = resources.getColor(R.color.result_view);
|
||||||
|
laserColor = resources.getColor(R.color.viewfinder_laser);
|
||||||
|
resultPointColor = resources.getColor(R.color.possible_result_points);
|
||||||
|
scannerAlpha = 0;
|
||||||
|
possibleResultPoints = new ArrayList<>(5);
|
||||||
|
lastPossibleResultPoints = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrameProvider(FrameProvider frameProvider) {
|
||||||
|
this.frameProvider = frameProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DrawAllocation")
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas canvas) {
|
||||||
|
if (frameProvider == null) {
|
||||||
|
return; // not ready yet, early draw before done configuring
|
||||||
|
}
|
||||||
|
Rect frame = this.frameProvider.getFramingRect();
|
||||||
|
Rect previewFrame = this.frameProvider.getFramingRectInPreview();
|
||||||
|
if (frame == null || previewFrame == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int width = canvas.getWidth();
|
||||||
|
int height = canvas.getHeight();
|
||||||
|
|
||||||
|
// Draw the exterior (i.e. outside the framing rect) darkened
|
||||||
|
paint.setColor(resultBitmap != null ? resultColor : maskColor);
|
||||||
|
canvas.drawRect(0, 0, width, frame.top, paint);
|
||||||
|
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
|
||||||
|
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
|
||||||
|
paint);
|
||||||
|
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
|
||||||
|
|
||||||
|
if (resultBitmap != null) {
|
||||||
|
// Draw the opaque result bitmap over the scanning rectangle
|
||||||
|
paint.setAlpha(CURRENT_POINT_OPACITY);
|
||||||
|
canvas.drawBitmap(resultBitmap, null, frame, paint);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Draw a red "laser scanner" line through the middle to show
|
||||||
|
// decoding is active
|
||||||
|
paint.setColor(laserColor);
|
||||||
|
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
|
||||||
|
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
|
||||||
|
int middle = frame.height() / 2 + frame.top;
|
||||||
|
canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1,
|
||||||
|
middle + 2, paint);
|
||||||
|
|
||||||
|
float scaleX = frame.width() / (float) previewFrame.width();
|
||||||
|
float scaleY = frame.height() / (float) previewFrame.height();
|
||||||
|
|
||||||
|
List<ResultPoint> currentPossible = possibleResultPoints;
|
||||||
|
List<ResultPoint> currentLast = lastPossibleResultPoints;
|
||||||
|
int frameLeft = frame.left;
|
||||||
|
int frameTop = frame.top;
|
||||||
|
if (currentPossible.isEmpty()) {
|
||||||
|
lastPossibleResultPoints = null;
|
||||||
|
} else {
|
||||||
|
possibleResultPoints = new ArrayList<>(5);
|
||||||
|
lastPossibleResultPoints = currentPossible;
|
||||||
|
paint.setAlpha(CURRENT_POINT_OPACITY);
|
||||||
|
paint.setColor(resultPointColor);
|
||||||
|
synchronized (currentPossible) {
|
||||||
|
for (ResultPoint point : currentPossible) {
|
||||||
|
canvas.drawCircle(
|
||||||
|
frameLeft + (int) (point.getX() * scaleX),
|
||||||
|
frameTop + (int) (point.getY() * scaleY),
|
||||||
|
POINT_SIZE, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentLast != null) {
|
||||||
|
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
|
||||||
|
paint.setColor(resultPointColor);
|
||||||
|
synchronized (currentLast) {
|
||||||
|
float radius = POINT_SIZE / 2.0f;
|
||||||
|
for (ResultPoint point : currentLast) {
|
||||||
|
canvas.drawCircle(
|
||||||
|
frameLeft + (int) (point.getX() * scaleX),
|
||||||
|
frameTop + (int) (point.getY() * scaleY),
|
||||||
|
radius, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request another update at the animation interval, but only
|
||||||
|
// repaint the laser line, not the entire viewfinder mask.
|
||||||
|
postInvalidateDelayed(ANIMATION_DELAY,
|
||||||
|
frame.left - POINT_SIZE,
|
||||||
|
frame.top - POINT_SIZE,
|
||||||
|
frame.right + POINT_SIZE,
|
||||||
|
frame.bottom + POINT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawViewfinder() {
|
||||||
|
Bitmap resultBitmap = this.resultBitmap;
|
||||||
|
this.resultBitmap = null;
|
||||||
|
if (resultBitmap != null) {
|
||||||
|
resultBitmap.recycle();
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a bitmap with the result points highlighted instead of the live
|
||||||
|
* scanning display.
|
||||||
|
*
|
||||||
|
* @param barcode An image of the decoded barcode.
|
||||||
|
*/
|
||||||
|
public void drawResultBitmap(Bitmap barcode) {
|
||||||
|
resultBitmap = barcode;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPossibleResultPoint(ResultPoint point) {
|
||||||
|
List<ResultPoint> points = possibleResultPoints;
|
||||||
|
synchronized (points) {
|
||||||
|
points.add(point);
|
||||||
|
int size = points.size();
|
||||||
|
if (size > MAX_RESULT_POINTS) {
|
||||||
|
// trim it
|
||||||
|
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FrameProvider {
|
||||||
|
|
||||||
|
Rect getFramingRect();
|
||||||
|
Rect getFramingRectInPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user