From 0e32139a89ae436eb9a2488a19402b485d1e4853 Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Fri, 6 May 2016 14:20:10 +0100
Subject: [PATCH] Second part of BQP UI improvements: zxing viewfinder.

---
 .../res/layout/fragment_keyagreement_qr.xml   |   5 +
 briar-android/res/values/color.xml            |   6 +
 .../keyagreement/ShowQrCodeFragment.java      |  18 +-
 .../briarproject/android/util/CameraView.java | 186 ++++++++++++++-
 .../android/util/QrCodeDecoder.java           |  13 +-
 .../android/util/ViewfinderView.java          | 214 ++++++++++++++++++
 6 files changed, 437 insertions(+), 5 deletions(-)
 create mode 100644 briar-android/src/org/briarproject/android/util/ViewfinderView.java

diff --git a/briar-android/res/layout/fragment_keyagreement_qr.xml b/briar-android/res/layout/fragment_keyagreement_qr.xml
index 5f1b175ed5..aa901c6738 100644
--- a/briar-android/res/layout/fragment_keyagreement_qr.xml
+++ b/briar-android/res/layout/fragment_keyagreement_qr.xml
@@ -10,6 +10,11 @@
 		android:layout_width="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
 		android:layout_width="match_parent"
 		android:layout_height="match_parent"
diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index 3fb5039785..613b553d82 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -43,4 +43,10 @@
 
 	<color name="spinner_border">#61000000</color> <!-- 38% Black -->
 	<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>
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
index 60c7ca85bd..e16c4973d1 100644
--- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
+++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
@@ -19,6 +19,8 @@ import android.widget.TextView;
 import android.widget.Toast;
 
 import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
 
 import org.briarproject.R;
 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.QrCodeDecoder;
 import org.briarproject.android.util.QrCodeUtils;
+import org.briarproject.android.util.ViewfinderView;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.KeyAgreementAbortedEvent;
 import org.briarproject.api.event.KeyAgreementFailedEvent;
@@ -55,7 +58,7 @@ import static java.util.logging.Level.WARNING;
 
 @SuppressWarnings("deprecation")
 public class ShowQrCodeFragment extends BaseEventFragment
-		implements QrCodeDecoder.ResultCallback {
+		implements QrCodeDecoder.ResultCallback, ResultPointCallback {
 
 	public static final String TAG = "ShowQrCodeFragment";
 
@@ -75,6 +78,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
 	protected Executor ioExecutor;
 
 	private CameraView cameraView;
+	private ViewfinderView viewfinderView;
 	private View statusView;
 	private TextView status;
 	private ImageView qrCode;
@@ -109,9 +113,13 @@ public class ShowQrCodeFragment extends BaseEventFragment
 		super.onViewCreated(view, savedInstanceState);
 
 		cameraView = (CameraView) view.findViewById(R.id.camera_view);
+		viewfinderView =
+				(ViewfinderView) view.findViewById(R.id.viewfinder_view);
 		statusView = view.findViewById(R.id.status_container);
 		status = (TextView) view.findViewById(R.id.connect_status);
 		qrCode = (ImageView) view.findViewById(R.id.qr_code);
+
+		viewfinderView.setFrameProvider(cameraView);
 	}
 
 	@Override
@@ -120,7 +128,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
 
 		getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
 
-		decoder = new QrCodeDecoder(this);
+		decoder = new QrCodeDecoder(this, this);
 	}
 
 	@Override
@@ -219,6 +227,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
 							getActivity().finish();
 						} else {
 							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 {
 
 		@Override
diff --git a/briar-android/src/org/briarproject/android/util/CameraView.java b/briar-android/src/org/briarproject/android/util/CameraView.java
index d3690b1895..dfec325af3 100644
--- a/briar-android/src/org/briarproject/android/util/CameraView.java
+++ b/briar-android/src/org/briarproject/android/util/CameraView.java
@@ -1,6 +1,8 @@
 package org.briarproject.android.util;
 
 import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.hardware.Camera;
 import android.hardware.Camera.AutoFocusCallback;
 import android.hardware.Camera.CameraInfo;
@@ -13,6 +15,7 @@ import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.logging.Logger;
@@ -31,17 +34,25 @@ import static java.util.logging.Level.WARNING;
 
 @SuppressWarnings("deprecation")
 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 MIN_FRAME_SIZE = 240;
+	private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
 	private static final Logger LOG =
 			Logger.getLogger(CameraView.class.getName());
 
 	private Camera camera = null;
+	private Rect framingRect;
+	private Rect framingRectInPreview;
+	private Rect framingRectInSensor;
 	private PreviewConsumer previewConsumer = null;
 	private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
 	private boolean autoFocus = false, surfaceExists = false;
 
+	private Point cameraResolution;
+	private final Object cameraResolutionLock = new Object();
+
 	public CameraView(Context context) {
 		super(context);
 	}
@@ -184,6 +195,24 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 			LOG.info("No suitable focus mode");
 		}
 		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) {
@@ -222,6 +251,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 			if (LOG.isLoggable(INFO))
 				LOG.info("Best size " + bestSize.width + "x" + 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);
 		}
 	}
+
+	/**
+	 * 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;
+	}
 }
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/util/QrCodeDecoder.java b/briar-android/src/org/briarproject/android/util/QrCodeDecoder.java
index 207adf368c..a6d65849ac 100644
--- a/briar-android/src/org/briarproject/android/util/QrCodeDecoder.java
+++ b/briar-android/src/org/briarproject/android/util/QrCodeDecoder.java
@@ -6,14 +6,18 @@ import android.hardware.Camera.Size;
 import android.os.AsyncTask;
 
 import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
 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.ResultPointCallback;
 import com.google.zxing.common.HybridBinarizer;
 import com.google.zxing.qrcode.QRCodeReader;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Logger;
 
 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 ResultCallback callback;
+	private final ResultPointCallback pointCallback;
 
 	private boolean stopped = false;
 
-	public QrCodeDecoder(ResultCallback callback) {
+	public QrCodeDecoder(ResultCallback callback,
+			ResultPointCallback pointCallback) {
 		this.callback = callback;
+		this.pointCallback = pointCallback;
 	}
 
 	public void start(Camera camera) {
@@ -72,9 +79,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
 			LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
 					height, 0, 0, width, height, false);
 			BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
+			Map<DecodeHintType, Object> hints = new HashMap<>();
+			hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
 			Result result = null;
 			try {
-				result = reader.decode(bitmap);
+				result = reader.decode(bitmap, hints);
 			} catch (ReaderException e) {
 				return null; // No barcode found
 			} catch (RuntimeException e) {
diff --git a/briar-android/src/org/briarproject/android/util/ViewfinderView.java b/briar-android/src/org/briarproject/android/util/ViewfinderView.java
new file mode 100644
index 0000000000..0e1836392e
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/util/ViewfinderView.java
@@ -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();
+	}
+}
-- 
GitLab