formats = EnumSet.noneOf(BarcodeFormat.class);
+ try {
+ for (String format : scanFormats) {
+ formats.add(BarcodeFormat.valueOf(format));
+ }
+ return formats;
+ } catch (IllegalArgumentException iae) {
+ // ignore it then
+ }
+ }
+ if (decodeMode != null) {
+ return FORMATS_FOR_MODE.get(decodeMode);
+ }
+ return null;
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/client/android/DecodeHintManager.java b/src/com/zhongyun/zxing/client/android/DecodeHintManager.java
new file mode 100644
index 0000000..ffa4a13
--- /dev/null
+++ b/src/com/zhongyun/zxing/client/android/DecodeHintManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.client.android;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.zxing.DecodeHintType;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * @author Lachezar Dobrev
+ */
+public final class DecodeHintManager {
+
+ private static final String TAG = DecodeHintManager.class.getSimpleName();
+
+ // This pattern is used in decoding integer arrays.
+ private static final Pattern COMMA = Pattern.compile(",");
+
+ private DecodeHintManager() {
+ }
+
+ /**
+ * Split a query string into a list of name-value pairs.
+ *
+ * This is an alternative to the {@link Uri#getQueryParameterNames()} and
+ * {@link Uri#getQueryParameters(String)}, which are quirky and not suitable
+ * for exist-only Uri parameters.
+ *
+ * This method ignores multiple parameters with the same name and returns the
+ * first one only. This is technically incorrect, but should be acceptable due
+ * to the method of processing Hints: no multiple values for a hint.
+ *
+ * @param query query to split
+ * @return name-value pairs
+ */
+ private static Map splitQuery(String query) {
+ Map map = new HashMap();
+ int pos = 0;
+ while (pos < query.length()) {
+ if (query.charAt(pos) == '&') {
+ // Skip consecutive ampersand separators.
+ pos++;
+ continue;
+ }
+ int amp = query.indexOf('&', pos);
+ int equ = query.indexOf('=', pos);
+ if (amp < 0) {
+ // This is the last element in the query, no more ampersand elements.
+ String name;
+ String text;
+ if (equ < 0) {
+ // No equal sign
+ name = query.substring(pos);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ text = "";
+ } else {
+ // Split name and text.
+ name = query.substring(pos, equ);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ text = query.substring(equ + 1);
+ text = text.replace('+', ' '); // Preemptively decode +
+ text = Uri.decode(text);
+ }
+ if (!map.containsKey(name)) {
+ map.put(name, text);
+ }
+ break;
+ }
+ if (equ < 0 || equ > amp) {
+ // No equal sign until the &: this is a simple parameter with no value.
+ String name = query.substring(pos, amp);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ if (!map.containsKey(name)) {
+ map.put(name, "");
+ }
+ pos = amp + 1;
+ continue;
+ }
+ String name = query.substring(pos, equ);
+ name = name.replace('+', ' '); // Preemptively decode +
+ name = Uri.decode(name);
+ String text = query.substring(equ + 1, amp);
+ text = text.replace('+', ' '); // Preemptively decode +
+ text = Uri.decode(text);
+ if (!map.containsKey(name)) {
+ map.put(name, text);
+ }
+ pos = amp + 1;
+ }
+ return map;
+ }
+
+ static Map parseDecodeHints(Uri inputUri) {
+ String query = inputUri.getEncodedQuery();
+ if (query == null || query.isEmpty()) {
+ return null;
+ }
+
+ // Extract parameters
+ Map parameters = splitQuery(query);
+
+ Map hints = new EnumMap(DecodeHintType.class);
+
+ for (DecodeHintType hintType : DecodeHintType.values()) {
+
+ if (hintType == DecodeHintType.CHARACTER_SET ||
+ hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+ hintType == DecodeHintType.POSSIBLE_FORMATS) {
+ continue; // This hint is specified in another way
+ }
+
+ String parameterName = hintType.name();
+ String parameterText = parameters.get(parameterName);
+ if (parameterText == null) {
+ continue;
+ }
+ if (hintType.getValueType().equals(Object.class)) {
+ // This is an unspecified type of hint content. Use the value as is.
+ // TODO: Can we make a different assumption on this?
+ hints.put(hintType, parameterText);
+ continue;
+ }
+ if (hintType.getValueType().equals(Void.class)) {
+ // Void hints are just flags: use the constant specified by DecodeHintType
+ hints.put(hintType, Boolean.TRUE);
+ continue;
+ }
+ if (hintType.getValueType().equals(String.class)) {
+ // A string hint: use the decoded value.
+ hints.put(hintType, parameterText);
+ continue;
+ }
+ if (hintType.getValueType().equals(Boolean.class)) {
+ // A boolean hint: a few values for false, everything else is true.
+ // An empty parameter is simply a flag-style parameter, assuming true
+ if (parameterText.isEmpty()) {
+ hints.put(hintType, Boolean.TRUE);
+ } else if ("0".equals(parameterText) ||
+ "false".equalsIgnoreCase(parameterText) ||
+ "no".equalsIgnoreCase(parameterText)) {
+ hints.put(hintType, Boolean.FALSE);
+ } else {
+ hints.put(hintType, Boolean.TRUE);
+ }
+
+ continue;
+ }
+ if (hintType.getValueType().equals(int[].class)) {
+ // An integer array. Used to specify valid lengths.
+ // Strip a trailing comma as in Java style array initialisers.
+ if (!parameterText.isEmpty() && parameterText.charAt(parameterText.length() - 1) == ',') {
+ parameterText = parameterText.substring(0, parameterText.length() - 1);
+ }
+ String[] values = COMMA.split(parameterText);
+ int[] array = new int[values.length];
+ for (int i = 0; i < values.length; i++) {
+ try {
+ array[i] = Integer.parseInt(values[i]);
+ } catch (NumberFormatException ignored) {
+ Log.w(TAG, "Skipping array of integers hint " + hintType + " due to invalid numeric value: '" + values[i] + '\'');
+ array = null;
+ break;
+ }
+ }
+ if (array != null) {
+ hints.put(hintType, array);
+ }
+ continue;
+ }
+ Log.w(TAG, "Unsupported hint type '" + hintType + "' of type " + hintType.getValueType());
+ }
+
+ Log.i(TAG, "Hints from the URI: " + hints);
+ return hints;
+ }
+
+ public static Map parseDecodeHints(Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null || extras.isEmpty()) {
+ return null;
+ }
+ Map hints = new EnumMap(DecodeHintType.class);
+
+ for (DecodeHintType hintType : DecodeHintType.values()) {
+
+ if (hintType == DecodeHintType.CHARACTER_SET ||
+ hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+ hintType == DecodeHintType.POSSIBLE_FORMATS) {
+ continue; // This hint is specified in another way
+ }
+
+ String hintName = hintType.name();
+ if (extras.containsKey(hintName)) {
+ if (hintType.getValueType().equals(Void.class)) {
+ // Void hints are just flags: use the constant specified by the DecodeHintType
+ hints.put(hintType, Boolean.TRUE);
+ } else {
+ Object hintData = extras.get(hintName);
+ if (hintType.getValueType().isInstance(hintData)) {
+ hints.put(hintType, hintData);
+ } else {
+ Log.w(TAG, "Ignoring hint " + hintType + " because it is not assignable from " + hintData);
+ }
+ }
+ }
+ }
+
+ Log.i(TAG, "Hints from the Intent: " + hints);
+ return hints;
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/client/android/InactivityTimer.java b/src/com/zhongyun/zxing/client/android/InactivityTimer.java
new file mode 100644
index 0000000..b50b1af
--- /dev/null
+++ b/src/com/zhongyun/zxing/client/android/InactivityTimer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.client.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+
+/**
+ * Finishes an context after a period of inactivity if the device is on battery power.
+ */
+public final class InactivityTimer {
+
+ private static final String TAG = InactivityTimer.class.getSimpleName();
+
+ private static final long INACTIVITY_DELAY_MS = 5 * 60 * 1000L;
+
+ private final Context context;
+ private final BroadcastReceiver powerStatusReceiver;
+ private boolean registered = false;
+ private Handler handler;
+ private Runnable callback;
+ private boolean onBattery;
+
+ public InactivityTimer(Context context, Runnable callback) {
+ this.context = context;
+ this.callback = callback;
+
+ powerStatusReceiver = new PowerStatusReceiver();
+ handler = new Handler();
+ }
+
+ /**
+ * Trigger activity, resetting the timer.
+ */
+ public void activity() {
+ cancelCallback();
+ if (onBattery) {
+ handler.postDelayed(callback, INACTIVITY_DELAY_MS);
+ }
+ }
+
+ /**
+ * Start the activity timer.
+ */
+ public void start() {
+ registerReceiver();
+ activity();
+ }
+
+ /**
+ * Cancel the activity timer.
+ */
+ public void cancel() {
+ cancelCallback();
+ unregisterReceiver();
+ }
+
+ private void unregisterReceiver() {
+ if (registered) {
+ context.unregisterReceiver(powerStatusReceiver);
+ registered = false;
+ }
+ }
+
+ private void registerReceiver() {
+ if (!registered) {
+ context.registerReceiver(powerStatusReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ registered = true;
+ }
+ }
+
+ private void cancelCallback() {
+ handler.removeCallbacksAndMessages(null);
+ }
+
+ private void onBattery(boolean onBattery) {
+ this.onBattery = onBattery;
+
+ // To make sure we're still running
+ if (registered) {
+ // This will either cancel or reschedule, depending on the battery status.
+ activity();
+ }
+ }
+
+ private final class PowerStatusReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ // 0 indicates that we're on battery
+ final boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0;
+ // post on handler to run in main thread
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ onBattery(onBatteryNow);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/client/android/Intents.java b/src/com/zhongyun/zxing/client/android/Intents.java
new file mode 100644
index 0000000..3915583
--- /dev/null
+++ b/src/com/zhongyun/zxing/client/android/Intents.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.client.android;
+
+/**
+ * This class provides the constants to use when sending an Intent to Barcode Scanner.
+ * These strings are effectively API and cannot be changed.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class Intents {
+ private Intents() {
+ }
+
+ public static final class Scan {
+ /**
+ * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return
+ * the results.
+ */
+ public static final String ACTION = "com.google.zxing.client.android.SCAN";
+
+ /**
+ * By default, sending this will decode all barcodes that we understand. However it
+ * may be useful to limit scanning to certain formats. Use
+ * {@link android.content.Intent#putExtra(String, String)} with one of the values below.
+ *
+ * Setting this is effectively shorthand for setting explicit formats with {@link #FORMATS}.
+ * It is overridden by that setting.
+ */
+ public static final String MODE = "SCAN_MODE";
+
+ /**
+ * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get
+ * prices, reviews, etc. for products.
+ */
+ public static final String PRODUCT_MODE = "PRODUCT_MODE";
+
+ /**
+ * Decode only 1D barcodes.
+ */
+ public static final String ONE_D_MODE = "ONE_D_MODE";
+
+ /**
+ * Decode only QR codes.
+ */
+ public static final String QR_CODE_MODE = "QR_CODE_MODE";
+
+ /**
+ * Decode only Data Matrix codes.
+ */
+ public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE";
+
+ /**
+ * Decode only Aztec.
+ */
+ public static final String AZTEC_MODE = "AZTEC_MODE";
+
+ /**
+ * Decode only PDF417.
+ */
+ public static final String PDF417_MODE = "PDF417_MODE";
+
+ /**
+ * Comma-separated list of formats to scan for. The values must match the names of
+ * {@link com.google.zxing.BarcodeFormat}s, e.g. {@link com.google.zxing.BarcodeFormat#EAN_13}.
+ * Example: "EAN_13,EAN_8,QR_CODE". This overrides {@link #MODE}.
+ */
+ public static final String FORMATS = "SCAN_FORMATS";
+
+ /**
+ * Optional parameter to specify the id of the camera from which to recognize barcodes.
+ * Overrides the default camera that would otherwise would have been selected.
+ * If provided, should be an int.
+ */
+ public static final String CAMERA_ID = "SCAN_CAMERA_ID";
+
+ /**
+ * @see com.google.zxing.DecodeHintType#CHARACTER_SET
+ */
+ public static final String CHARACTER_SET = "CHARACTER_SET";
+
+ /**
+ * Set to false to disable beep. Defaults to true.
+ */
+ public static final String BEEP_ENABLED = "BEEP_ENABLED";
+
+ /**
+ * Set to true to return a path to the barcode's image as it was captured. Defaults to false.
+ */
+ public static final String BARCODE_IMAGE_ENABLED = "BARCODE_IMAGE_ENABLED";
+
+ /**
+ * Whether or not the orientation should be locked when the activity is first started.
+ * Defaults to true.
+ */
+ public static final String ORIENTATION_LOCKED = "SCAN_ORIENTATION_LOCKED";
+
+ /**
+ * Prompt to show on-screen when scanning by intent. Specified as a {@link String}.
+ */
+ public static final String PROMPT_MESSAGE = "PROMPT_MESSAGE";
+
+ /**
+ * If a barcode is found, Barcodes returns {@link android.app.Activity#RESULT_OK} to
+ * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
+ * of the app which requested the scan via
+ * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
+ * The barcodes contents can be retrieved with
+ * {@link android.content.Intent#getStringExtra(String)}.
+ * If the user presses Back, the result code will be {@link android.app.Activity#RESULT_CANCELED}.
+ */
+ public static final String RESULT = "SCAN_RESULT";
+
+ /**
+ * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_FORMAT}
+ * to determine which barcode format was found.
+ * See {@link com.google.zxing.BarcodeFormat} for possible values.
+ */
+ public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT";
+
+ /**
+ * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_UPC_EAN_EXTENSION}
+ * to return the content of any UPC extension barcode that was also found. Only applicable
+ * to {@link com.google.zxing.BarcodeFormat#UPC_A} and {@link com.google.zxing.BarcodeFormat#EAN_13}
+ * formats.
+ */
+ public static final String RESULT_UPC_EAN_EXTENSION = "SCAN_RESULT_UPC_EAN_EXTENSION";
+
+ /**
+ * Call {@link android.content.Intent#getByteArrayExtra(String)} with {@link #RESULT_BYTES}
+ * to get a {@code byte[]} of raw bytes in the barcode, if available.
+ */
+ public static final String RESULT_BYTES = "SCAN_RESULT_BYTES";
+
+ /**
+ * Key for the value of {@link com.google.zxing.ResultMetadataType#ORIENTATION}, if available.
+ * Call {@link android.content.Intent#getIntArrayExtra(String)} with {@link #RESULT_ORIENTATION}.
+ */
+ public static final String RESULT_ORIENTATION = "SCAN_RESULT_ORIENTATION";
+
+ /**
+ * Key for the value of {@link com.google.zxing.ResultMetadataType#ERROR_CORRECTION_LEVEL}, if available.
+ * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_ERROR_CORRECTION_LEVEL}.
+ */
+ public static final String RESULT_ERROR_CORRECTION_LEVEL = "SCAN_RESULT_ERROR_CORRECTION_LEVEL";
+
+ /**
+ * Prefix for keys that map to the values of {@link com.google.zxing.ResultMetadataType#BYTE_SEGMENTS},
+ * if available. The actual values will be set under a series of keys formed by adding 0, 1, 2, ...
+ * to this prefix. So the first byte segment is under key "SCAN_RESULT_BYTE_SEGMENTS_0" for example.
+ * Call {@link android.content.Intent#getByteArrayExtra(String)} with these keys.
+ */
+ public static final String RESULT_BYTE_SEGMENTS_PREFIX = "SCAN_RESULT_BYTE_SEGMENTS_";
+
+ /**
+ * Call {@link android.content.Intent#getStringExtra(String)} with {@link #SCAN_RESULT_IMAGE_PATH}
+ * to get a {@code String} path to a cropped and compressed png file of the barcode's image
+ * as it was displayed. Only available if
+ * {@link com.google.zxing.integration.android.IntentIntegrator#setBarcodeImageEnabled(boolean)}
+ * is called with true.
+ */
+ public static final String RESULT_BARCODE_IMAGE_PATH = "SCAN_RESULT_IMAGE_PATH";
+
+ private Scan() {
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/client/android/camera/CameraConfigurationUtils.java b/src/com/zhongyun/zxing/client/android/camera/CameraConfigurationUtils.java
new file mode 100644
index 0000000..82ae604
--- /dev/null
+++ b/src/com/zhongyun/zxing/client/android/camera/CameraConfigurationUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.client.android.camera;
+
+import android.annotation.TargetApi;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for configuring the Android camera.
+ *
+ * @author Sean Owen
+ */
+public final class CameraConfigurationUtils {
+
+ private static final String TAG = "CameraConfiguration";
+
+ private static final Pattern SEMICOLON = Pattern.compile(";");
+
+ private static final float MAX_EXPOSURE_COMPENSATION = 1.5f;
+ private static final float MIN_EXPOSURE_COMPENSATION = 0.0f;
+ private static final int MIN_FPS = 10;
+ private static final int MAX_FPS = 20;
+ private static final int AREA_PER_1000 = 400;
+
+ private CameraConfigurationUtils() {
+ }
+
+ public static void setFocus(Camera.Parameters parameters,
+ boolean autoFocus,
+ boolean disableContinuous,
+ boolean safeMode) {
+ List supportedFocusModes = parameters.getSupportedFocusModes();
+ String focusMode = null;
+ if (autoFocus) {
+ if (safeMode || disableContinuous) {
+ focusMode = findSettableValue("focus mode",
+ supportedFocusModes,
+ Camera.Parameters.FOCUS_MODE_AUTO);
+ } else {
+ focusMode = findSettableValue("focus mode",
+ supportedFocusModes,
+ Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
+ Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
+ Camera.Parameters.FOCUS_MODE_AUTO);
+ }
+ }
+ // Maybe selected auto-focus but not available, so fall through here:
+ if (!safeMode && focusMode == null) {
+ focusMode = findSettableValue("focus mode",
+ supportedFocusModes,
+ Camera.Parameters.FOCUS_MODE_MACRO,
+ Camera.Parameters.FOCUS_MODE_EDOF);
+ }
+ if (focusMode != null) {
+ if (focusMode.equals(parameters.getFocusMode())) {
+ Log.i(TAG, "Focus mode already set to " + focusMode);
+ } else {
+ parameters.setFocusMode(focusMode);
+ }
+ }
+ }
+
+ public static void setTorch(Camera.Parameters parameters, boolean on) {
+ List supportedFlashModes = parameters.getSupportedFlashModes();
+ String flashMode;
+ if (on) {
+ flashMode = findSettableValue("flash mode",
+ supportedFlashModes,
+ Camera.Parameters.FLASH_MODE_TORCH,
+ Camera.Parameters.FLASH_MODE_ON);
+ } else {
+ flashMode = findSettableValue("flash mode",
+ supportedFlashModes,
+ Camera.Parameters.FLASH_MODE_OFF);
+ }
+ if (flashMode != null) {
+ if (flashMode.equals(parameters.getFlashMode())) {
+ Log.i(TAG, "Flash mode already set to " + flashMode);
+ } else {
+ Log.i(TAG, "Setting flash mode to " + flashMode);
+ parameters.setFlashMode(flashMode);
+ }
+ }
+ }
+
+ public static void setBestExposure(Camera.Parameters parameters, boolean lightOn) {
+ int minExposure = parameters.getMinExposureCompensation();
+ int maxExposure = parameters.getMaxExposureCompensation();
+ float step = parameters.getExposureCompensationStep();
+ if ((minExposure != 0 || maxExposure != 0) && step > 0.0f) {
+ // Set low when light is on
+ float targetCompensation = lightOn ? MIN_EXPOSURE_COMPENSATION : MAX_EXPOSURE_COMPENSATION;
+ int compensationSteps = Math.round(targetCompensation / step);
+ float actualCompensation = step * compensationSteps;
+ // Clamp value:
+ compensationSteps = Math.max(Math.min(compensationSteps, maxExposure), minExposure);
+ if (parameters.getExposureCompensation() == compensationSteps) {
+ Log.i(TAG, "Exposure compensation already set to " + compensationSteps + " / " + actualCompensation);
+ } else {
+ Log.i(TAG, "Setting exposure compensation to " + compensationSteps + " / " + actualCompensation);
+ parameters.setExposureCompensation(compensationSteps);
+ }
+ } else {
+ Log.i(TAG, "Camera does not support exposure compensation");
+ }
+ }
+
+ public static void setBestPreviewFPS(Camera.Parameters parameters) {
+ setBestPreviewFPS(parameters, MIN_FPS, MAX_FPS);
+ }
+
+ public static void setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS) {
+ List supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange();
+ Log.i(TAG, "Supported FPS ranges: " + toString(supportedPreviewFpsRanges));
+ if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) {
+ int[] suitableFPSRange = null;
+ for (int[] fpsRange : supportedPreviewFpsRanges) {
+ int thisMin = fpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+ int thisMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+ if (thisMin >= minFPS * 1000 && thisMax <= maxFPS * 1000) {
+ suitableFPSRange = fpsRange;
+ break;
+ }
+ }
+ if (suitableFPSRange == null) {
+ Log.i(TAG, "No suitable FPS range?");
+ } else {
+ int[] currentFpsRange = new int[2];
+ parameters.getPreviewFpsRange(currentFpsRange);
+ if (Arrays.equals(currentFpsRange, suitableFPSRange)) {
+ Log.i(TAG, "FPS range already set to " + Arrays.toString(suitableFPSRange));
+ } else {
+ Log.i(TAG, "Setting FPS range to " + Arrays.toString(suitableFPSRange));
+ parameters.setPreviewFpsRange(suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ public static void setFocusArea(Camera.Parameters parameters) {
+ if (parameters.getMaxNumFocusAreas() > 0) {
+ Log.i(TAG, "Old focus areas: " + toString(parameters.getFocusAreas()));
+ List middleArea = buildMiddleArea(AREA_PER_1000);
+ Log.i(TAG, "Setting focus area to : " + toString(middleArea));
+ parameters.setFocusAreas(middleArea);
+ } else {
+ Log.i(TAG, "Device does not support focus areas");
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ public static void setMetering(Camera.Parameters parameters) {
+ if (parameters.getMaxNumMeteringAreas() > 0) {
+ Log.i(TAG, "Old metering areas: " + parameters.getMeteringAreas());
+ List middleArea = buildMiddleArea(AREA_PER_1000);
+ Log.i(TAG, "Setting metering area to : " + toString(middleArea));
+ parameters.setMeteringAreas(middleArea);
+ } else {
+ Log.i(TAG, "Device does not support metering areas");
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ private static List buildMiddleArea(int areaPer1000) {
+ return Collections.singletonList(
+ new Camera.Area(new Rect(-areaPer1000, -areaPer1000, areaPer1000, areaPer1000), 1));
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ public static void setVideoStabilization(Camera.Parameters parameters) {
+ if (parameters.isVideoStabilizationSupported()) {
+ if (parameters.getVideoStabilization()) {
+ Log.i(TAG, "Video stabilization already enabled");
+ } else {
+ Log.i(TAG, "Enabling video stabilization...");
+ parameters.setVideoStabilization(true);
+ }
+ } else {
+ Log.i(TAG, "This device does not support video stabilization");
+ }
+ }
+
+ public static void setBarcodeSceneMode(Camera.Parameters parameters) {
+ if (Camera.Parameters.SCENE_MODE_BARCODE.equals(parameters.getSceneMode())) {
+ Log.i(TAG, "Barcode scene mode already set");
+ return;
+ }
+ String sceneMode = findSettableValue("scene mode",
+ parameters.getSupportedSceneModes(),
+ Camera.Parameters.SCENE_MODE_BARCODE);
+ if (sceneMode != null) {
+ parameters.setSceneMode(sceneMode);
+ }
+ }
+
+ public static void setZoom(Camera.Parameters parameters, double targetZoomRatio) {
+ if (parameters.isZoomSupported()) {
+ Integer zoom = indexOfClosestZoom(parameters, targetZoomRatio);
+ if (zoom == null) {
+ return;
+ }
+ if (parameters.getZoom() == zoom) {
+ Log.i(TAG, "Zoom is already set to " + zoom);
+ } else {
+ Log.i(TAG, "Setting zoom to " + zoom);
+ parameters.setZoom(zoom);
+ }
+ } else {
+ Log.i(TAG, "Zoom is not supported");
+ }
+ }
+
+ private static Integer indexOfClosestZoom(Camera.Parameters parameters, double targetZoomRatio) {
+ List ratios = parameters.getZoomRatios();
+ Log.i(TAG, "Zoom ratios: " + ratios);
+ int maxZoom = parameters.getMaxZoom();
+ if (ratios == null || ratios.isEmpty() || ratios.size() != maxZoom + 1) {
+ Log.w(TAG, "Invalid zoom ratios!");
+ return null;
+ }
+ double target100 = 100.0 * targetZoomRatio;
+ double smallestDiff = Double.POSITIVE_INFINITY;
+ int closestIndex = 0;
+ for (int i = 0; i < ratios.size(); i++) {
+ double diff = Math.abs(ratios.get(i) - target100);
+ if (diff < smallestDiff) {
+ smallestDiff = diff;
+ closestIndex = i;
+ }
+ }
+ Log.i(TAG, "Chose zoom ratio of " + (ratios.get(closestIndex) / 100.0));
+ return closestIndex;
+ }
+
+ public static void setInvertColor(Camera.Parameters parameters) {
+ if (Camera.Parameters.EFFECT_NEGATIVE.equals(parameters.getColorEffect())) {
+ Log.i(TAG, "Negative effect already set");
+ return;
+ }
+ String colorMode = findSettableValue("color effect",
+ parameters.getSupportedColorEffects(),
+ Camera.Parameters.EFFECT_NEGATIVE);
+ if (colorMode != null) {
+ parameters.setColorEffect(colorMode);
+ }
+ }
+
+ private static String findSettableValue(String name,
+ Collection supportedValues,
+ String... desiredValues) {
+ Log.i(TAG, "Requesting " + name + " value from among: " + Arrays.toString(desiredValues));
+ Log.i(TAG, "Supported " + name + " values: " + supportedValues);
+ if (supportedValues != null) {
+ for (String desiredValue : desiredValues) {
+ if (supportedValues.contains(desiredValue)) {
+ Log.i(TAG, "Can set " + name + " to: " + desiredValue);
+ return desiredValue;
+ }
+ }
+ }
+ Log.i(TAG, "No supported values match");
+ return null;
+ }
+
+ private static String toString(Collection arrays) {
+ if (arrays == null || arrays.isEmpty()) {
+ return "[]";
+ }
+ StringBuilder buffer = new StringBuilder();
+ buffer.append('[');
+ Iterator it = arrays.iterator();
+ while (it.hasNext()) {
+ buffer.append(Arrays.toString(it.next()));
+ if (it.hasNext()) {
+ buffer.append(", ");
+ }
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ private static String toString(Iterable areas) {
+ if (areas == null) {
+ return null;
+ }
+ StringBuilder result = new StringBuilder();
+ for (Camera.Area area : areas) {
+ result.append(area.rect).append(':').append(area.weight).append(' ');
+ }
+ return result.toString();
+ }
+
+ public static String collectStats(Camera.Parameters parameters) {
+ return collectStats(parameters.flatten());
+ }
+
+ public static String collectStats(CharSequence flattenedParams) {
+ StringBuilder result = new StringBuilder(1000);
+
+ result.append("BOARD=").append(Build.BOARD).append('\n');
+ result.append("BRAND=").append(Build.BRAND).append('\n');
+ result.append("CPU_ABI=").append(Build.CPU_ABI).append('\n');
+ result.append("DEVICE=").append(Build.DEVICE).append('\n');
+ result.append("DISPLAY=").append(Build.DISPLAY).append('\n');
+ result.append("FINGERPRINT=").append(Build.FINGERPRINT).append('\n');
+ result.append("HOST=").append(Build.HOST).append('\n');
+ result.append("ID=").append(Build.ID).append('\n');
+ result.append("MANUFACTURER=").append(Build.MANUFACTURER).append('\n');
+ result.append("MODEL=").append(Build.MODEL).append('\n');
+ result.append("PRODUCT=").append(Build.PRODUCT).append('\n');
+ result.append("TAGS=").append(Build.TAGS).append('\n');
+ result.append("TIME=").append(Build.TIME).append('\n');
+ result.append("TYPE=").append(Build.TYPE).append('\n');
+ result.append("USER=").append(Build.USER).append('\n');
+ result.append("VERSION.CODENAME=").append(Build.VERSION.CODENAME).append('\n');
+ result.append("VERSION.INCREMENTAL=").append(Build.VERSION.INCREMENTAL).append('\n');
+ result.append("VERSION.RELEASE=").append(Build.VERSION.RELEASE).append('\n');
+ result.append("VERSION.SDK_INT=").append(Build.VERSION.SDK_INT).append('\n');
+
+ if (flattenedParams != null) {
+ String[] params = SEMICOLON.split(flattenedParams);
+ Arrays.sort(params);
+ for (String param : params) {
+ result.append(param).append('\n');
+ }
+ }
+
+ return result.toString();
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/client/android/camera/OpenCameraInterface.java b/src/com/zhongyun/zxing/client/android/camera/OpenCameraInterface.java
new file mode 100644
index 0000000..51d96fa
--- /dev/null
+++ b/src/com/zhongyun/zxing/client/android/camera/OpenCameraInterface.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.client.android.camera;
+
+import android.hardware.Camera;
+import android.util.Log;
+
+public final class OpenCameraInterface {
+
+ private static final String TAG = OpenCameraInterface.class.getName();
+
+ private OpenCameraInterface() {
+ }
+
+ /**
+ * For {@link #open(int)}, means no preference for which camera to open.
+ */
+ public static final int NO_REQUESTED_CAMERA = -1;
+
+ public static int getCameraId(int requestedId) {
+ int numCameras = Camera.getNumberOfCameras();
+ if (numCameras == 0) {
+ Log.w(TAG, "No cameras!");
+ return -1;
+ }
+
+ int cameraId = requestedId;
+
+ boolean explicitRequest = cameraId >= 0;
+
+ if (!explicitRequest) {
+ // Select a camera if no explicit camera requested
+ int index = 0;
+ while (index < numCameras) {
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(index, cameraInfo);
+ if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+ break;
+ }
+ index++;
+ }
+
+ cameraId = index;
+ }
+
+ if (cameraId < numCameras) {
+ return cameraId;
+ } else {
+ if (explicitRequest) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Opens the requested camera with {@link Camera#open(int)}, if one exists.
+ *
+ * @param requestedId camera ID of the camera to use. A negative value
+ * or {@link #NO_REQUESTED_CAMERA} means "no preference"
+ * @return handle to {@link Camera} that was opened
+ */
+ public static Camera open(int requestedId) {
+ int cameraId = getCameraId(requestedId);
+ if (cameraId == -1) {
+ return null;
+ } else {
+ return Camera.open(cameraId);
+ }
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeCallback.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeCallback.java
new file mode 100644
index 0000000..2a0f106
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeCallback.java
@@ -0,0 +1,28 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.google.zxing.ResultPoint;
+
+import java.util.List;
+
+/**
+ * Callback that is notified when a barcode is scanned.
+ */
+public interface BarcodeCallback {
+ /**
+ * Barcode was successfully scanned.
+ *
+ * @param result the result
+ */
+ void barcodeResult(BarcodeResult result);
+
+ /**
+ * ResultPoints are detected. This may be called whether or not the scanning was successful.
+ *
+ * This is mainly useful to give some feedback to the user while scanning.
+ *
+ * Do not depend on this being called at any specific point in the decode cycle.
+ *
+ * @param resultPoints points potentially identifying a barcode
+ */
+ void possibleResultPoints(List resultPoints);
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeEncoder.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeEncoder.java
new file mode 100644
index 0000000..7fb9c83
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeEncoder.java
@@ -0,0 +1,78 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.graphics.Bitmap;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * Helper class for encoding barcodes as a Bitmap.
+ *
+ * Adapted from QRCodeEncoder, from the zxing project:
+ * https://github.com/zxing/zxing
+ *
+ * Licensed under the Apache License, Version 2.0.
+ */
+public class BarcodeEncoder {
+ private static final int WHITE = 0xFFFFFFFF;
+ private static final int BLACK = 0xFF000000;
+
+
+ public BarcodeEncoder() {
+ }
+
+ public Bitmap createBitmap(BitMatrix matrix) {
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ int[] pixels = new int[width * height];
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = matrix.get(x, y) ? BLACK : WHITE;
+ }
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ }
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) throws WriterException {
+ try {
+ return new MultiFormatWriter().encode(contents, format, width, height);
+ } catch (WriterException e) {
+ throw e;
+ } catch (Exception e) {
+ // ZXing sometimes throws an IllegalArgumentException
+ throw new WriterException(e);
+ }
+ }
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map hints) throws WriterException {
+ try {
+ return new MultiFormatWriter().encode(contents, format, width, height, hints);
+ } catch (WriterException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new WriterException(e);
+ }
+ }
+
+ public Bitmap encodeBitmap(String contents, BarcodeFormat format, int width, int height) throws WriterException {
+ return createBitmap(encode(contents, format, width, height));
+ }
+
+ public Bitmap encodeBitmap(String contents, BarcodeFormat format, int width, int height, Map hints) throws WriterException {
+ return createBitmap(encode(contents, format, width, height, hints));
+ }
+
+
+
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeResult.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeResult.java
new file mode 100644
index 0000000..622bbbb
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeResult.java
@@ -0,0 +1,156 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+
+import java.util.Map;
+
+/**
+ * This contains the result of a barcode scan.
+ *
+ * This class delegate all read-only fields of {@link com.google.zxing.Result},
+ * and adds a bitmap with scanned barcode.
+ */
+public class BarcodeResult {
+ private static final float PREVIEW_LINE_WIDTH = 4.0f;
+ private static final float PREVIEW_DOT_WIDTH = 10.0f;
+
+ protected Result mResult;
+ protected SourceData sourceData;
+
+ private final int mScaleFactor = 2;
+
+ public BarcodeResult(Result result, SourceData sourceData) {
+ this.mResult = result;
+ this.sourceData = sourceData;
+ }
+
+ private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, int scaleFactor) {
+ if (a != null && b != null) {
+ canvas.drawLine(a.getX() / scaleFactor,
+ a.getY() / scaleFactor,
+ b.getX() / scaleFactor,
+ b.getY() / scaleFactor,
+ paint);
+ }
+ }
+
+ /**
+ * @return wrapped {@link com.google.zxing.Result}
+ */
+ public Result getResult() {
+ return mResult;
+ }
+
+ /**
+ * @return {@link Bitmap} with barcode preview
+ * @see #getBitmapWithResultPoints(int)
+ */
+ public Bitmap getBitmap() {
+ return sourceData.getBitmap(mScaleFactor);
+ }
+
+ /**
+ * @param color Color of result points
+ * @return {@link Bitmap} with result points on it, or plain bitmap, if no result points
+ */
+ public Bitmap getBitmapWithResultPoints(int color) {
+ Bitmap bitmap = getBitmap();
+ Bitmap barcode = bitmap;
+ ResultPoint[] points = mResult.getResultPoints();
+
+ if (points != null && points.length > 0 && bitmap != null) {
+ barcode = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(barcode);
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ Paint paint = new Paint();
+ paint.setColor(color);
+ if (points.length == 2) {
+ paint.setStrokeWidth(PREVIEW_LINE_WIDTH);
+ drawLine(canvas, paint, points[0], points[1], mScaleFactor);
+ } else if (points.length == 4 &&
+ (mResult.getBarcodeFormat() == BarcodeFormat.UPC_A ||
+ mResult.getBarcodeFormat() == BarcodeFormat.EAN_13)) {
+ // Hacky special case -- draw two lines, for the barcode and metadata
+ drawLine(canvas, paint, points[0], points[1], mScaleFactor);
+ drawLine(canvas, paint, points[2], points[3], mScaleFactor);
+ } else {
+ paint.setStrokeWidth(PREVIEW_DOT_WIDTH);
+ for (ResultPoint point : points) {
+ if (point != null) {
+ canvas.drawPoint(point.getX() / mScaleFactor, point.getY() / mScaleFactor, paint);
+ }
+ }
+ }
+ }
+ return barcode;
+ }
+
+ /**
+ *
+ * @return Bitmap preview scale factor
+ */
+ public int getBitmapScaleFactor(){
+ return mScaleFactor;
+ }
+
+ /**
+ * @return raw text encoded by the barcode
+ * @see Result#getText()
+ */
+ public String getText() {
+ return mResult.getText();
+ }
+
+ /**
+ * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null}
+ * @see Result#getRawBytes()
+ */
+ public byte[] getRawBytes() {
+ return mResult.getRawBytes();
+ }
+
+ /**
+ * @return points related to the barcode in the image. These are typically points
+ * identifying finder patterns or the corners of the barcode. The exact meaning is
+ * specific to the type of barcode that was decoded.
+ * @see Result#getResultPoints()
+ */
+ public ResultPoint[] getResultPoints() {
+ return mResult.getResultPoints();
+ }
+
+ /**
+ * @return {@link BarcodeFormat} representing the format of the barcode that was decoded
+ * @see Result#getBarcodeFormat()
+ */
+ public BarcodeFormat getBarcodeFormat() {
+ return mResult.getBarcodeFormat();
+ }
+
+ /**
+ * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be
+ * {@code null}. This contains optional metadata about what was detected about the barcode,
+ * like orientation.
+ * @see Result#getResultMetadata()
+ */
+ public Map getResultMetadata() {
+ return mResult.getResultMetadata();
+ }
+
+ public long getTimestamp() {
+ return mResult.getTimestamp();
+ }
+
+ @Override
+ public String toString() {
+ return mResult.getText();
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeView.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeView.java
new file mode 100644
index 0000000..4af716f
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/BarcodeView.java
@@ -0,0 +1,212 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ResultPoint;
+import com.zhongyun.viewer.R;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view for scanning barcodes.
+ *
+ * Two methods MUST be called to manage the state:
+ * 1. resume() - initialize the camera and start the preview. Call from the Activity's onResume().
+ * 2. pause() - stop the preview and release any resources. Call from the Activity's onPause().
+ *
+ * Start decoding with decodeSingle() or decodeContinuous(). Stop decoding with stopDecoding().
+ *
+ * @see CameraPreview for more details on the preview lifecycle.
+ */
+public class BarcodeView extends CameraPreview {
+
+ private enum DecodeMode {
+ NONE,
+ SINGLE,
+ CONTINUOUS
+ }
+
+ private DecodeMode decodeMode = DecodeMode.NONE;
+ private BarcodeCallback callback = null;
+ private DecoderThread decoderThread;
+
+ private DecoderFactory decoderFactory;
+
+
+ private Handler resultHandler;
+
+ private final Handler.Callback resultCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == R.id.zxing_decode_succeeded) {
+ BarcodeResult result = (BarcodeResult) message.obj;
+
+ if (result != null) {
+ if (callback != null && decodeMode != DecodeMode.NONE) {
+ callback.barcodeResult(result);
+ if (decodeMode == DecodeMode.SINGLE) {
+ stopDecoding();
+ }
+ }
+ }
+ return true;
+ } else if (message.what == R.id.zxing_decode_failed) {
+ // Failed. Next preview is automatically tried.
+ return true;
+ } else if (message.what == R.id.zxing_possible_result_points) {
+ List resultPoints = (List) message.obj;
+ if (callback != null && decodeMode != DecodeMode.NONE) {
+ callback.possibleResultPoints(resultPoints);
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+
+ public BarcodeView(Context context) {
+ super(context);
+ initialize(context, null);
+ }
+
+ public BarcodeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context, attrs);
+ }
+
+ public BarcodeView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context, attrs);
+ }
+
+
+ private void initialize(Context context, AttributeSet attrs) {
+ decoderFactory = new DefaultDecoderFactory();
+ resultHandler = new Handler(resultCallback);
+ }
+
+
+ /**
+ * Set the DecoderFactory to use. Use this to specify the formats to decode.
+ *
+ * Call this from UI thread only.
+ *
+ * @param decoderFactory the DecoderFactory creating Decoders.
+ * @see DefaultDecoderFactory
+ */
+ public void setDecoderFactory(DecoderFactory decoderFactory) {
+ Util.validateMainThread();
+
+ this.decoderFactory = decoderFactory;
+ if (this.decoderThread != null) {
+ this.decoderThread.setDecoder(createDecoder());
+ }
+ }
+
+ private Decoder createDecoder() {
+ if (decoderFactory == null) {
+ decoderFactory = createDefaultDecoderFactory();
+ }
+ DecoderResultPointCallback callback = new DecoderResultPointCallback();
+ Map hints = new HashMap();
+ hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, callback);
+ Decoder decoder = this.decoderFactory.createDecoder(hints);
+ callback.setDecoder(decoder);
+ return decoder;
+ }
+
+ /**
+ *
+ * @return the current DecoderFactory in use.
+ */
+ public DecoderFactory getDecoderFactory() {
+ return decoderFactory;
+ }
+
+ /**
+ * Decode a single barcode, then stop decoding.
+ *
+ * The callback will only be called on the UI thread.
+ *
+ * @param callback called with the barcode result, as well as possible ResultPoints
+ */
+ public void decodeSingle(BarcodeCallback callback) {
+ this.decodeMode = DecodeMode.SINGLE;
+ this.callback = callback;
+ startDecoderThread();
+ }
+
+
+ /**
+ * Continuously decode barcodes. The same barcode may be returned multiple times per second.
+ *
+ * The callback will only be called on the UI thread.
+ *
+ * @param callback called with the barcode result, as well as possible ResultPoints
+ */
+ public void decodeContinuous(BarcodeCallback callback) {
+ this.decodeMode = DecodeMode.CONTINUOUS;
+ this.callback = callback;
+ startDecoderThread();
+ }
+
+ /**
+ * Stop decoding, but do not stop the preview.
+ */
+ public void stopDecoding() {
+ this.decodeMode = DecodeMode.NONE;
+ this.callback = null;
+ stopDecoderThread();
+ }
+
+ protected DecoderFactory createDefaultDecoderFactory() {
+ return new DefaultDecoderFactory();
+ }
+
+ private void startDecoderThread() {
+ stopDecoderThread(); // To be safe
+
+ if (decodeMode != DecodeMode.NONE && isPreviewActive()) {
+ // We only start the thread if both:
+ // 1. decoding was requested
+ // 2. the preview is active
+ decoderThread = new DecoderThread(getCameraInstance(), createDecoder(), resultHandler);
+ decoderThread.setCropRect(getPreviewFramingRect());
+ decoderThread.start();
+ }
+ }
+
+ @Override
+ protected void previewStarted() {
+ super.previewStarted();
+
+ startDecoderThread();
+ }
+
+ private void stopDecoderThread() {
+ if (decoderThread != null) {
+ decoderThread.stop();
+ decoderThread = null;
+ }
+ }
+ /**
+ * Stops the live preview and decoding.
+ *
+ * Call from the Activity's onPause() method.
+ */
+ @Override
+ public void pause() {
+ stopDecoderThread();
+
+ super.pause();
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/CameraPreview.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CameraPreview.java
new file mode 100644
index 0000000..d25d395
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CameraPreview.java
@@ -0,0 +1,589 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.zhongyun.viewer.R;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.CameraInstance;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.CameraSettings;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.DisplayConfiguration;
+
+/**
+ * CameraPreview is a view that handles displaying of a camera preview on a SurfaceView. It is
+ * intended to be used as a base for realtime processing of camera images, e.g. barcode decoding
+ * or OCR, although none of this happens in CameraPreview itself.
+ *
+ * The camera is managed on a separate thread, using CameraInstance.
+ *
+ * Two methods MUST be called on CameraPreview to manage its state:
+ * 1. resume() - initialize the camera and start the preview. Call from the Activity's onResume().
+ * 2. pause() - stop the preview and release any resources. Call from the Activity's onPause().
+ *
+ * Startup sequence:
+ *
+ * 1. Create SurfaceView.
+ * 2. open camera.
+ * 2. layout this container, to get size
+ * 3. set display config, according to the container size
+ * 4. configure()
+ * 5. wait for preview size to be ready
+ * 6. set surface size according to preview size
+ * 7. set surface and start preview
+ */
+public class CameraPreview extends ViewGroup {
+ public interface StateListener {
+ /**
+ * Preview and frame sizes are determined.
+ */
+ void previewSized();
+
+ /**
+ * Preview has started.
+ */
+ void previewStarted();
+
+ /**
+ * Preview has stopped.
+ */
+ void previewStopped();
+
+ /**
+ * The camera has errored, and cannot display a preview.
+ *
+ * @param error the error
+ */
+ void cameraError(Exception error);
+ }
+
+ private static final String TAG = CameraPreview.class.getSimpleName();
+
+ private CameraInstance cameraInstance;
+
+ private WindowManager windowManager;
+
+ private Handler stateHandler;
+
+ private SurfaceView surfaceView;
+
+ private boolean previewActive = false;
+
+ private RotationListener rotationListener;
+
+ private List stateListeners = new ArrayList();
+
+ private DisplayConfiguration displayConfiguration;
+ private CameraSettings cameraSettings = new CameraSettings();
+
+ // Size of this container, non-null after layout is performed
+ private Size containerSize;
+
+ // Size of the preview resolution
+ private Size previewSize;
+
+ // Rect placing the preview surface
+ private Rect surfaceRect;
+
+ // Size of the current surface. non-null if the surface is ready
+ private Size currentSurfaceSize;
+
+ // Framing rectangle relative to this view
+ private Rect framingRect = null;
+
+ // Framing rectangle relative to the preview resolution
+ private Rect previewFramingRect = null;
+
+ // Size of the framing rectangle. If null, defaults to using a margin percentage.
+ private Size framingRectSize = null;
+
+ // Fraction of the width / heigth to use as a margin. This fraction is used on each size, so
+ // must be smaller than 0.5;
+ private double marginFraction = 0.1d;
+
+ private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ currentSurfaceSize = null;
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (holder == null) {
+ Log.e(TAG, "*** WARNING *** surfaceChanged() gave us a null surface!");
+ return;
+ }
+ currentSurfaceSize = new Size(width, height);
+ startPreviewIfReady();
+ }
+ };
+
+ private final Handler.Callback stateCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == R.id.zxing_prewiew_size_ready) {
+ previewSized((Size) message.obj);
+ return true;
+ } else if (message.what == R.id.zxing_camera_error) {
+ Exception error = (Exception) message.obj;
+
+ if (isActive()) {
+ // This check prevents multiple errors from begin passed through.
+ pause();
+ fireState.cameraError(error);
+ }
+ }
+ return false;
+ }
+ };
+
+ private RotationCallback rotationCallback = new RotationCallback() {
+ @Override
+ public void onRotationChanged(int rotation) {
+ // Make sure this is run on the main thread.
+ stateHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ rotationChanged();
+ }
+ });
+ }
+ };
+
+ public CameraPreview(Context context) {
+ super(context);
+ initialize(context, null, 0, 0);
+ }
+
+ public CameraPreview(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context, attrs, 0, 0);
+ }
+
+ public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context, attrs, defStyleAttr, 0);
+ }
+
+
+ private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ if (getBackground() == null) {
+ // Default to SurfaceView colour, so that there are less changes.
+ setBackgroundColor(Color.BLACK);
+ }
+
+ TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.zxing_camera_preview);
+ int framingRectWidth = (int) attributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_width, -1);
+ int framingRectHeight = (int) attributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_height, -1);
+ attributes.recycle();
+
+ if (framingRectWidth > 0 && framingRectHeight > 0) {
+ this.framingRectSize = new Size(framingRectWidth, framingRectHeight);
+ }
+
+ windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ stateHandler = new Handler(stateCallback);
+
+ setupSurfaceView();
+
+ rotationListener = new RotationListener();
+ }
+
+ private void rotationChanged() {
+ pause();
+ resume();
+ }
+
+ private void setupSurfaceView() {
+ surfaceView = new SurfaceView(getContext());
+ if (Build.VERSION.SDK_INT < 11) {
+ surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ }
+ surfaceView.getHolder().addCallback(surfaceCallback);
+ addView(surfaceView);
+ }
+
+ /**
+ * Add a listener to be notified of changes to the preview state, as well as camera errors.
+ *
+ * @param listener the listener
+ */
+ public void addStateListener(StateListener listener) {
+ stateListeners.add(listener);
+ }
+
+ private final StateListener fireState = new StateListener() {
+ @Override
+ public void previewSized() {
+ for (StateListener listener : stateListeners) {
+ listener.previewSized();
+ }
+ }
+
+ @Override
+ public void previewStarted() {
+ for (StateListener listener : stateListeners) {
+ listener.previewStarted();
+ }
+
+ }
+
+ @Override
+ public void previewStopped() {
+ for (StateListener listener : stateListeners) {
+ listener.previewStopped();
+ }
+ }
+
+ @Override
+ public void cameraError(Exception error) {
+ for (StateListener listener : stateListeners) {
+ listener.cameraError(error);
+ }
+ }
+ };
+
+ private void calculateFrames() {
+ if (containerSize == null || previewSize == null || displayConfiguration == null) {
+ previewFramingRect = null;
+ framingRect = null;
+ surfaceRect = null;
+ throw new IllegalStateException("containerSize or previewSize is not set yet");
+ }
+
+ int previewWidth = previewSize.width;
+ int previewHeight = previewSize.height;
+
+ int width = containerSize.width;
+ int height = containerSize.height;
+
+ surfaceRect = displayConfiguration.scalePreview(previewSize);
+
+ Rect container = new Rect(0, 0, width, height);
+ framingRect = calculateFramingRect(container, surfaceRect);
+ Rect frameInPreview = new Rect(framingRect);
+ frameInPreview.offset(-surfaceRect.left, -surfaceRect.top);
+
+ previewFramingRect = new Rect(frameInPreview.left * previewWidth / surfaceRect.width(),
+ frameInPreview.top * previewHeight / surfaceRect.height(),
+ frameInPreview.right * previewWidth / surfaceRect.width(),
+ frameInPreview.bottom * previewHeight / surfaceRect.height());
+
+ if (previewFramingRect.width() <= 0 || previewFramingRect.height() <= 0) {
+ previewFramingRect = null;
+ framingRect = null;
+ Log.w(TAG, "Preview frame is too small");
+ } else {
+ fireState.previewSized();
+ }
+ }
+
+ /**
+ * Call this on the main thread, while the preview is running.
+ *
+ * @param on true to turn on the torch
+ */
+ public void setTorch(boolean on) {
+ if (cameraInstance != null) {
+ cameraInstance.setTorch(on);
+ }
+ }
+
+ private void containerSized(Size containerSize) {
+ this.containerSize = containerSize;
+ if (cameraInstance != null) {
+ if (cameraInstance.getDisplayConfiguration() == null) {
+ displayConfiguration = new DisplayConfiguration(getDisplayRotation(), containerSize);
+ cameraInstance.setDisplayConfiguration(displayConfiguration);
+ cameraInstance.configureCamera();
+ }
+ }
+ }
+
+ private void previewSized(Size size) {
+ this.previewSize = size;
+ if (containerSize != null) {
+ calculateFrames();
+ requestLayout();
+ startPreviewIfReady();
+ }
+ }
+
+ private void startPreviewIfReady() {
+ if (currentSurfaceSize != null && previewSize != null && surfaceRect != null) {
+ if (currentSurfaceSize.equals(new Size(surfaceRect.width(), surfaceRect.height()))) {
+ startCameraPreview(surfaceView.getHolder());
+ } else {
+ // Surface is not the correct size yet
+ }
+ }
+ }
+
+ @SuppressLint("DrawAllocation")
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ containerSized(new Size(r - l, b - t));
+
+ if (surfaceRect == null) {
+ // Match the container, to reduce the risk of issues. The preview should never be drawn
+ // while the surface has this size.
+ surfaceView.layout(0, 0, getWidth(), getHeight());
+ } else {
+ surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom);
+ }
+ }
+
+ /**
+ * The framing rectangle, relative to this view. Use to draw the rectangle.
+ *
+ * Will never be null while the preview is active.
+ *
+ * @return the framing rect, or null
+ * @see #isPreviewActive()
+ */
+ public Rect getFramingRect() {
+ return framingRect;
+ }
+
+ /**
+ * The framing rect, relative to the camera preview resolution.
+ *
+ * Will never be null while the preview is active.
+ *
+ * @return the preview rect, or null
+ * @see #isPreviewActive()
+ */
+ public Rect getPreviewFramingRect() {
+ return previewFramingRect;
+ }
+
+ /**
+ * @return the CameraSettings currently in use
+ */
+ public CameraSettings getCameraSettings() {
+ return cameraSettings;
+ }
+
+ /**
+ * Set the CameraSettings. Use this to select a different camera, change exposure and torch
+ * settings, and some other options.
+ *
+ * This has no effect if the camera is already open.
+ *
+ * @param cameraSettings the new settings
+ */
+ public void setCameraSettings(CameraSettings cameraSettings) {
+ this.cameraSettings = cameraSettings;
+ }
+
+ /**
+ * Start the camera preview and decoding. Typically this should be called from the Activity's
+ * onResume() method.
+ *
+ * Call from UI thread only.
+ */
+ public void resume() {
+ // This must be safe to call multiple times
+ Util.validateMainThread();
+ Log.d(TAG, "resume()");
+
+ // initCamera() does nothing if called twice, but does log a warning
+ initCamera();
+
+ if (currentSurfaceSize != null) {
+ // The activity was paused but not stopped, so the surface still exists. Therefore
+ // surfaceCreated() won't be called, so init the camera here.
+ startPreviewIfReady();
+ } else {
+ // Install the callback and wait for surfaceCreated() to init the camera.
+ surfaceView.getHolder().addCallback(surfaceCallback);
+ }
+
+ // To trigger surfaceSized again
+ requestLayout();
+ rotationListener.listen(getContext(), rotationCallback);
+ }
+
+
+ /**
+ * Pause scanning and the camera preview. Typically this should be called from the Activity's
+ * onPause() method.
+ *
+ * Call from UI thread only.
+ */
+ public void pause() {
+ // This must be safe to call multiple times.
+ Util.validateMainThread();
+ Log.d(TAG, "pause()");
+
+ if (cameraInstance != null) {
+ cameraInstance.close();
+ cameraInstance = null;
+ previewActive = false;
+ }
+ if (currentSurfaceSize == null) {
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ surfaceHolder.removeCallback(surfaceCallback);
+ }
+
+ this.containerSize = null;
+ this.previewSize = null;
+ this.previewFramingRect = null;
+ rotationListener.stop();
+
+ fireState.previewStopped();
+ }
+
+ public Size getFramingRectSize() {
+ return framingRectSize;
+ }
+
+ /**
+ * Set an exact size for the framing rectangle. It will be centered in the view.
+ *
+ * @param framingRectSize the size
+ */
+ public void setFramingRectSize(Size framingRectSize) {
+ this.framingRectSize = framingRectSize;
+ }
+
+ public double getMarginFraction() {
+ return marginFraction;
+ }
+
+ /**
+ * The the fraction of the width/height of view to be used as a margin for the framing rect.
+ * This is ignored if framingRectSize is specified.
+ *
+ * @param marginFraction the fraction
+ */
+ public void setMarginFraction(double marginFraction) {
+ if(marginFraction >= 0.5d) {
+ throw new IllegalArgumentException("The margin fraction must be less than 0.5");
+ }
+ this.marginFraction = marginFraction;
+ }
+
+ /**
+ * Considered active if between resume() and pause().
+ *
+ * @return true if active
+ */
+ protected boolean isActive() {
+ return cameraInstance != null;
+ }
+
+ private int getDisplayRotation() {
+ return windowManager.getDefaultDisplay().getRotation();
+ }
+
+ private void initCamera() {
+ if (cameraInstance != null) {
+ Log.w(TAG, "initCamera called twice");
+ return;
+ }
+
+ cameraInstance = new CameraInstance(getContext());
+ cameraInstance.setCameraSettings(cameraSettings);
+
+ cameraInstance.setReadyHandler(stateHandler);
+ cameraInstance.open();
+ }
+
+
+ private void startCameraPreview(SurfaceHolder holder) {
+ if (!previewActive) {
+ Log.i(TAG, "Starting preview");
+ cameraInstance.setSurfaceHolder(holder);
+ cameraInstance.startPreview();
+ previewActive = true;
+
+ previewStarted();
+ fireState.previewStarted();
+ }
+ }
+
+ /**
+ * Called when the preview is started. Override this to start decoding work.
+ */
+ protected void previewStarted() {
+
+ }
+
+ /**
+ * Get the current CameraInstance. This may be null, and may change when
+ * pausing / resuming the preview.
+ *
+ * While the preview is active, getCameraInstance() will never be null.
+ *
+ * @return the current CameraInstance
+ * @see #isPreviewActive()
+ */
+ public CameraInstance getCameraInstance() {
+ return cameraInstance;
+ }
+
+ /**
+ * The preview typically starts being active a while after calling resume(), and stops
+ * when calling pause().
+ *
+ * @return true if the preview is active
+ */
+ public boolean isPreviewActive() {
+ return previewActive;
+ }
+
+
+ /**
+ * Calculate framing rectangle, relative to the preview frame.
+ *
+ * Note that the SurfaceView may be larger than the container.
+ *
+ * Override this for more control over the framing rect calculations.
+ *
+ * @param container this container, with left = top = 0
+ * @param surface the SurfaceView, relative to this container
+ * @return the framing rect, relative to this container
+ */
+ protected Rect calculateFramingRect(Rect container, Rect surface) {
+ // intersection is the part of the container that is used for the preview
+ Rect intersection = new Rect(container);
+ boolean intersects = intersection.intersect(surface);
+
+ if(framingRectSize != null) {
+ // Specific size is specified. Make sure it's not larger than the container or surface.
+ int horizontalMargin = Math.max(0, (intersection.width() - framingRectSize.width) / 2);
+ int verticalMargin = Math.max(0, (intersection.height() - framingRectSize.height) / 2);
+ intersection.inset(horizontalMargin, verticalMargin);
+ return intersection;
+ }
+ // margin as 10% (default) of the smaller of width, height
+ int margin = (int)Math.min(intersection.width() * marginFraction, intersection.height() * marginFraction);
+ intersection.inset(margin, margin);
+ if (intersection.height() > intersection.width()) {
+ // We don't want a frame that is taller than wide.
+ intersection.inset(0, (intersection.height() - intersection.width()) / 2);
+ }
+ return intersection;
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureActivity.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureActivity.java
new file mode 100644
index 0000000..177fd22
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureActivity.java
@@ -0,0 +1,60 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.zhongyun.viewer.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.WindowManager;
+
+
+/**
+ *
+ */
+public class CaptureActivity extends Activity {
+ private CaptureManager capture;
+ private CompoundBarcodeView barcodeScannerView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ setContentView(R.layout.zxing_capture);
+ barcodeScannerView = (CompoundBarcodeView)findViewById(R.id.zxing_barcode_scanner);
+
+ capture = new CaptureManager(this, barcodeScannerView);
+ capture.initializeFromIntent(getIntent(), savedInstanceState);
+ capture.decode();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ capture.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ capture.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ capture.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ capture.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureManager.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureManager.java
new file mode 100644
index 0000000..7725bfe
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CaptureManager.java
@@ -0,0 +1,347 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.zhongyun.zxing.client.android.BeepManager;
+import com.zhongyun.zxing.client.android.InactivityTimer;
+import com.zhongyun.zxing.client.android.Intents;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages barcode scanning for a CaptureActivity. This class may be used to have a custom Activity
+ * (e.g. with a customized look and feel, or a different superclass), but not the barcode scanning
+ * process itself.
+ *
+ * This is intended for an Activity that is dedicated to capturing a single barcode and returning
+ * it via setResult(). For other use cases, use DefaultBarcodeScannerView or BarcodeView directly.
+ *
+ * The following is managed by this class:
+ * - Orientation lock
+ * - InactivityTimer
+ * - BeepManager
+ * - Initializing from an Intent (via IntentIntegrator)
+ * - Setting the result and finishing the Activity when a barcode is scanned
+ * - Displaying camera errors
+ */
+public class CaptureManager {
+ private static final String TAG = CaptureManager.class.getSimpleName();
+
+ private Activity activity;
+ private CompoundBarcodeView barcodeView;
+ private int orientationLock = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ private static final String SAVED_ORIENTATION_LOCK = "SAVED_ORIENTATION_LOCK";
+ private boolean returnBarcodeImagePath = false;
+
+ private boolean destroyed = false;
+
+ // Delay long enough that the beep can be played.
+ // TODO: play beep in background
+ private static final long DELAY_BEEP = 150;
+
+ private InactivityTimer inactivityTimer;
+ private BeepManager beepManager;
+
+ private Handler handler;
+
+ private BarcodeCallback callback = new BarcodeCallback() {
+ @Override
+ public void barcodeResult(final BarcodeResult result) {
+ barcodeView.pause();
+ beepManager.playBeepSoundAndVibrate();
+
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ returnResult(result);
+ }
+ }, DELAY_BEEP);
+
+ }
+
+ @Override
+ public void possibleResultPoints(List resultPoints) {
+
+ }
+ };
+
+ private final CameraPreview.StateListener stateListener = new CameraPreview.StateListener() {
+ @Override
+ public void previewSized() {
+
+ }
+
+ @Override
+ public void previewStarted() {
+
+ }
+
+ @Override
+ public void previewStopped() {
+
+ }
+
+ @Override
+ public void cameraError(Exception error) {
+ displayFrameworkBugMessageAndExit();
+ }
+ };
+
+ public CaptureManager(Activity activity, CompoundBarcodeView barcodeView) {
+ this.activity = activity;
+ this.barcodeView = barcodeView;
+ barcodeView.getBarcodeView().addStateListener(stateListener);
+
+ handler = new Handler();
+
+ inactivityTimer = new InactivityTimer(activity, new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Finishing due to inactivity");
+ finish();
+ }
+ });
+
+ beepManager = new BeepManager(activity);
+ }
+
+ /**
+ * Perform initialization, according to preferences set in the intent.
+ *
+ * @param intent the intent containing the scanning preferences
+ * @param savedInstanceState saved state, containing orientation lock
+ */
+ public void initializeFromIntent(Intent intent, Bundle savedInstanceState) {
+ Window window = activity.getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ if (savedInstanceState != null) {
+ // If the screen was locked and unlocked again, we may start in a different orientation
+ // (even one not allowed by the manifest). In this case we restore the orientation we were
+ // previously locked to.
+ this.orientationLock = savedInstanceState.getInt(SAVED_ORIENTATION_LOCK, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ if(intent != null) {
+ if (orientationLock == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ // Only lock the orientation if it's not locked to something else yet
+ boolean orientationLocked = intent.getBooleanExtra(Intents.Scan.ORIENTATION_LOCKED, true);
+
+ if (orientationLocked) {
+ lockOrientation();
+ }
+ }
+
+ if (Intents.Scan.ACTION.equals(intent.getAction())) {
+ barcodeView.initializeFromIntent(intent);
+ }
+
+ if (!intent.getBooleanExtra(Intents.Scan.BEEP_ENABLED, true)) {
+ beepManager.setBeepEnabled(false);
+ beepManager.updatePrefs();
+ }
+
+ if (intent.getBooleanExtra(Intents.Scan.BARCODE_IMAGE_ENABLED, false)) {
+ returnBarcodeImagePath = true;
+ }
+ }
+ }
+
+ /**
+ * Lock display to current orientation.
+ */
+ protected void lockOrientation() {
+ // Only get the orientation if it's not locked to one yet.
+ if (this.orientationLock == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ // Adapted from http://stackoverflow.com/a/14565436
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ int rotation = display.getRotation();
+ int baseOrientation = activity.getResources().getConfiguration().orientation;
+ int orientation = 0;
+ if (baseOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ }
+ } else if (baseOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ }
+ }
+
+ this.orientationLock = orientation;
+ }
+ //noinspection ResourceType
+ activity.setRequestedOrientation(this.orientationLock);
+ }
+
+ /**
+ * Start decoding.
+ */
+ public void decode() {
+ barcodeView.decodeSingle(callback);
+ }
+
+ /**
+ * Call from Activity#onResume().
+ */
+ public void onResume() {
+ barcodeView.resume();
+ beepManager.updatePrefs();
+ inactivityTimer.start();
+ }
+
+ /**
+ * Call from Activity#onPause().
+ */
+ public void onPause() {
+ barcodeView.pause();
+
+ inactivityTimer.cancel();
+ beepManager.close();
+ }
+
+ /**
+ * Call from Activity#onDestroy().
+ */
+ public void onDestroy() {
+ destroyed = true;
+ inactivityTimer.cancel();
+ }
+
+ /**
+ * Call from Activity#onSaveInstanceState().
+ */
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(SAVED_ORIENTATION_LOCK, this.orientationLock);
+ }
+
+
+ /**
+ * Create a intent to return as the Activity result.
+ *
+ * @param rawResult the BarcodeResult, must not be null.
+ * @param barcodeImagePath a path to an exported file of the Barcode Image, can be null.
+ * @return the Intent
+ */
+ public static Intent resultIntent(BarcodeResult rawResult, String barcodeImagePath) {
+ Intent intent = new Intent(Intents.Scan.ACTION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
+ intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
+ Log.e("zhongquan","rawResult = " + rawResult);
+ byte[] rawBytes = rawResult.getRawBytes();
+ if (rawBytes != null && rawBytes.length > 0) {
+ intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
+ }
+ Map metadata = rawResult.getResultMetadata();
+ if (metadata != null) {
+ if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
+ intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
+ metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
+ }
+ Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
+ if (orientation != null) {
+ intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
+ }
+ String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
+ if (ecLevel != null) {
+ intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments = (Iterable) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
+ if (byteSegments != null) {
+ int i = 0;
+ for (byte[] byteSegment : byteSegments) {
+ intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
+ i++;
+ }
+ }
+ }
+ if (barcodeImagePath != null) {
+ intent.putExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH, barcodeImagePath);
+ }
+ return intent;
+ }
+
+ /**
+ * Save the barcode image to a temporary file stored in the application's cache, and return its path.
+ * Only does so if returnBarcodeImagePath is enabled.
+ *
+ * @param rawResult the BarcodeResult, must not be null
+ * @return the path or null
+ */
+ private String getBarcodeImagePath(BarcodeResult rawResult) {
+ String barcodeImagePath = null;
+ if (returnBarcodeImagePath) {
+ Bitmap bmp = rawResult.getBitmap();
+ try {
+ File bitmapFile = File.createTempFile("barcodeimage", ".jpg", activity.getCacheDir());
+ FileOutputStream outputStream = new FileOutputStream(bitmapFile);
+ bmp.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+ outputStream.close();
+ barcodeImagePath = bitmapFile.getAbsolutePath();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to create temporary file and store bitmap! " + e);
+ }
+ }
+ return barcodeImagePath;
+ }
+
+ private void finish() {
+ activity.finish();
+ }
+
+
+ protected void returnResult(BarcodeResult rawResult) {
+ Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
+ activity.setResult(Activity.RESULT_OK, intent);
+ finish();
+ }
+
+ protected void displayFrameworkBugMessageAndExit() {
+ if (activity.isFinishing() || this.destroyed) {
+ return;
+ }
+// AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+// builder.setTitle(activity.getString(R.string.zxing_app_name));
+// builder.setMessage(activity.getString(R.string.zxing_msg_camera_framework_bug));
+// builder.setPositiveButton(R.string.zxing_button_ok, new DialogInterface.OnClickListener() {
+// @Override
+// public void onClick(DialogInterface dialog, int which) {
+// finish();
+// }
+// });
+// builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+// @Override
+// public void onCancel(DialogInterface dialog) {
+// finish();
+// }
+// });
+// builder.show();
+ }
+
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/CompoundBarcodeView.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CompoundBarcodeView.java
new file mode 100644
index 0000000..b2b1136
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/CompoundBarcodeView.java
@@ -0,0 +1,264 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ResultPoint;
+import com.zhongyun.viewer.R;
+import com.zhongyun.zxing.client.android.DecodeFormatManager;
+import com.zhongyun.zxing.client.android.DecodeHintManager;
+import com.zhongyun.zxing.client.android.Intents;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.CameraSettings;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates BarcodeView, ViewfinderView and status text.
+ *
+ * To customize the UI, use BarcodeView and ViewfinderView directly.
+ */
+public class CompoundBarcodeView extends FrameLayout {
+ private BarcodeView barcodeView;
+ private ViewfinderView viewFinder;
+ private TextView statusView;
+
+ /**
+ * The instance of @link TorchListener to send events callback.
+ */
+ private TorchListener torchListener;
+
+ private class WrappedCallback implements BarcodeCallback {
+ private BarcodeCallback delegate;
+
+ public WrappedCallback(BarcodeCallback delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void barcodeResult(BarcodeResult result) {
+ delegate.barcodeResult(result);
+ }
+
+ @Override
+ public void possibleResultPoints(List resultPoints) {
+ for (ResultPoint point : resultPoints) {
+ viewFinder.addPossibleResultPoint(point);
+ }
+ delegate.possibleResultPoints(resultPoints);
+ }
+ }
+
+ public CompoundBarcodeView(Context context) {
+ super(context);
+ initialize();
+ }
+
+ public CompoundBarcodeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(attrs);
+ }
+
+ public CompoundBarcodeView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(attrs);
+ }
+
+ /**
+ * Initialize the view with the xml configuration based on styleable attributes.
+ *
+ * @param attrs The attributes to use on view.
+ */
+ private void initialize(AttributeSet attrs) {
+ // Get attributes set on view
+ TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_view);
+
+ int scannerLayout = attributes.getResourceId(
+ R.styleable.zxing_view_zxing_scanner_layout, R.layout.zxing_barcode_scanner);
+
+ attributes.recycle();
+
+ inflate(getContext(), scannerLayout, this);
+
+ barcodeView = (BarcodeView) findViewById(R.id.zxing_barcode_surface);
+
+ if (barcodeView == null) {
+ throw new IllegalArgumentException(
+ "There is no a com.journeyapps.barcodescanner.BarcodeView on provided layout " +
+ "with the id \"zxing_barcode_surface\".");
+ }
+
+ viewFinder = (ViewfinderView) findViewById(R.id.zxing_viewfinder_view);
+
+ if (viewFinder == null) {
+ throw new IllegalArgumentException(
+ "There is no a com.journeyapps.barcodescanner.ViewfinderView on provided layout " +
+ "with the id \"zxing_viewfinder_view\".");
+ }
+
+ viewFinder.setCameraPreview(barcodeView);
+
+ // statusView is optional
+ statusView = (TextView) findViewById(R.id.zxing_status_view);
+ }
+
+ /**
+ * Initialize with no custom attributes setted.
+ */
+ private void initialize() {
+ initialize(null);
+ }
+
+ /**
+ * Convenience method to initialize camera id, decode formats and prompt message from an intent.
+ *
+ * @param intent the intent, as generated by IntentIntegrator
+ */
+ public void initializeFromIntent(Intent intent) {
+ // Scan the formats the intent requested, and return the result to the calling activity.
+ Set decodeFormats = DecodeFormatManager.parseDecodeFormats(intent);
+ Map decodeHints = DecodeHintManager.parseDecodeHints(intent);
+
+ CameraSettings settings = new CameraSettings();
+
+ if (intent.hasExtra(Intents.Scan.CAMERA_ID)) {
+ int cameraId = intent.getIntExtra(Intents.Scan.CAMERA_ID, -1);
+ if (cameraId >= 0) {
+ settings.setRequestedCameraId(cameraId);
+ }
+ }
+
+ String customPromptMessage = intent.getStringExtra(Intents.Scan.PROMPT_MESSAGE);
+ if (customPromptMessage != null) {
+ setStatusText(customPromptMessage);
+ }
+
+ String characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET);
+
+ MultiFormatReader reader = new MultiFormatReader();
+ reader.setHints(decodeHints);
+
+ barcodeView.setCameraSettings(settings);
+ barcodeView.setDecoderFactory(new DefaultDecoderFactory(decodeFormats, decodeHints, characterSet));
+ }
+
+ public void setStatusText(String text) {
+ // statusView is optional when using a custom layout
+ if(statusView != null) {
+ statusView.setText(text);
+ }
+ }
+
+
+ /**
+ * @see BarcodeView#pause()
+ */
+ public void pause() {
+ barcodeView.pause();
+ }
+
+ /**
+ * @see BarcodeView#resume()
+ */
+ public void resume() {
+ barcodeView.resume();
+ }
+
+ public BarcodeView getBarcodeView() {
+ return (BarcodeView) findViewById(R.id.zxing_barcode_surface);
+ }
+
+ public ViewfinderView getViewFinder() {
+ return viewFinder;
+ }
+
+ public TextView getStatusView() {
+ return statusView;
+ }
+
+
+ /**
+ * @see BarcodeView#decodeSingle(BarcodeCallback)
+ */
+ public void decodeSingle(BarcodeCallback callback) {
+ barcodeView.decodeSingle(new WrappedCallback(callback));
+
+ }
+
+ /**
+ * @see BarcodeView#decodeContinuous(BarcodeCallback)
+ */
+ public void decodeContinuous(BarcodeCallback callback) {
+ barcodeView.decodeContinuous(new WrappedCallback(callback));
+ }
+
+ /**
+ * Turn on the device's flashlight.
+ */
+ public void setTorchOn() {
+ barcodeView.setTorch(true);
+
+ if (torchListener != null) {
+ torchListener.onTorchOn();
+ }
+ }
+
+ /**
+ * Turn off the device's flashlight.
+ */
+ public void setTorchOff() {
+ barcodeView.setTorch(false);
+
+ if (torchListener != null) {
+ torchListener.onTorchOff();
+ }
+ }
+
+ /**
+ * Handles focus, camera, volume up and volume down keys.
+ *
+ * Note that this view is not usually focused, so the Activity should call this directly.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_CAMERA:
+ // Handle these events so they don't launch the Camera app
+ return true;
+ // Use volume up/down to turn on light
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ setTorchOff();
+ return true;
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ setTorchOn();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ public void setTorchListener(TorchListener listener) {
+ this.torchListener = listener;
+ }
+
+ /**
+ * The Listener to torch/fflashlight events (turn on, turn off).
+ */
+ public interface TorchListener {
+
+ void onTorchOn();
+
+ void onTorchOff();
+
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/Decoder.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Decoder.java
new file mode 100644
index 0000000..7e911d8
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Decoder.java
@@ -0,0 +1,104 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for decoding images.
+ *
+ * A decoder contains all the configuration required for the binarization and decoding process.
+ *
+ * The actual decoding should happen on a dedicated thread.
+ */
+public class Decoder implements ResultPointCallback {
+ private Reader reader;
+
+ /**
+ * Create a new Decoder with the specified Reader.
+ *
+ * It is recommended to use an instance of MultiFormatReader in most cases.
+ *
+ * @param reader the reader
+ */
+ public Decoder(Reader reader) {
+ this.reader = reader;
+ }
+
+ protected Reader getReader() {
+ return reader;
+ }
+
+ /**
+ * Given an image source, attempt to decode the barcode.
+ *
+ * Must not raise an exception.
+ *
+ * @param source the image source
+ * @return a Result or null
+ */
+ public Result decode(LuminanceSource source) {
+ return decode(toBitmap(source));
+ }
+
+ /**
+ * Given an image source, convert to a binary bitmap.
+ *
+ * Override this to use a custom binarizer.
+ *
+ * @param source the image source
+ * @return a BinaryBitmap
+ */
+ protected BinaryBitmap toBitmap(LuminanceSource source) {
+ return new BinaryBitmap(new HybridBinarizer(source));
+ }
+
+ /**
+ * Decode a binary bitmap.
+ *
+ * @param bitmap the binary bitmap
+ * @return a Result or null
+ */
+ protected Result decode(BinaryBitmap bitmap) {
+ possibleResultPoints.clear();
+ try {
+ if (reader instanceof MultiFormatReader) {
+ // Optimization - MultiFormatReader's normal decode() method is slow.
+ return ((MultiFormatReader) reader).decodeWithState(bitmap);
+ } else {
+ return reader.decode(bitmap);
+ }
+ } catch (Exception e) {
+ // Decode error, try again next frame
+ return null;
+ } finally {
+ reader.reset();
+ }
+ }
+
+ private List possibleResultPoints = new ArrayList();
+
+ /**
+ * Call immediately after decode(), from the same thread.
+ *
+ * The result is undefined while decode() is running.
+ *
+ * @return possible ResultPoints from the last decode.
+ */
+ public List getPossibleResultPoints() {
+ return new ArrayList(possibleResultPoints);
+ }
+
+ @Override
+ public void foundPossibleResultPoint(ResultPoint point) {
+ possibleResultPoints.add(point);
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderFactory.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderFactory.java
new file mode 100644
index 0000000..c1c43d7
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderFactory.java
@@ -0,0 +1,24 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.google.zxing.DecodeHintType;
+
+import java.util.Map;
+
+/**
+ * Factory to create Decoder instances. Typically one instance will be created per DecoderThread.
+ *
+ * @see DefaultDecoderFactory
+ */
+public interface DecoderFactory {
+
+ /**
+ * Create a new Decoder.
+ *
+ * While this method will only be called from a single thread, the created Decoder will
+ * be used from a different thread. Each decoder will only be used from a single thread.
+ *
+ * @param baseHints default hints. Typically specifies DecodeHintType.NEED_RESULT_POINT_CALLBACK.
+ * @return a new Decoder
+ */
+ Decoder createDecoder(Map baseHints);
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderResultPointCallback.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderResultPointCallback.java
new file mode 100644
index 0000000..cddae81
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderResultPointCallback.java
@@ -0,0 +1,33 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+
+/**
+ * ResultPointCallback delegating the ResultPoints to a decoder.
+ */
+public class DecoderResultPointCallback implements ResultPointCallback {
+ private Decoder decoder;
+
+ public DecoderResultPointCallback(Decoder decoder) {
+ this.decoder = decoder;
+ }
+
+ public DecoderResultPointCallback() {
+ }
+
+ public Decoder getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(Decoder decoder) {
+ this.decoder = decoder;
+ }
+
+ @Override
+ public void foundPossibleResultPoint(ResultPoint point) {
+ if(decoder != null) {
+ decoder.foundPossibleResultPoint(point);
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderThread.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderThread.java
new file mode 100644
index 0000000..01e0f97
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DecoderThread.java
@@ -0,0 +1,166 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.zhongyun.viewer.R;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.CameraInstance;
+import com.zhongyun.zxing.journeyapps.barcodescanner.camera.PreviewCallback;
+
+import java.util.List;
+
+/**
+ *
+ */
+public class DecoderThread {
+ private static final String TAG = DecoderThread.class.getSimpleName();
+
+ private CameraInstance cameraInstance;
+ private HandlerThread thread;
+ private Handler handler;
+ private Decoder decoder;
+ private Handler resultHandler;
+ private Rect cropRect;
+ private boolean running = false;
+ private final Object LOCK = new Object();
+
+ private final Handler.Callback callback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == R.id.zxing_decode) {
+ decode((SourceData) message.obj);
+ }
+ return true;
+ }
+ };
+
+ public DecoderThread(CameraInstance cameraInstance, Decoder decoder, Handler resultHandler) {
+ Util.validateMainThread();
+
+ this.cameraInstance = cameraInstance;
+ this.decoder = decoder;
+ this.resultHandler = resultHandler;
+ }
+
+ public Decoder getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(Decoder decoder) {
+ this.decoder = decoder;
+ }
+
+ public Rect getCropRect() {
+ return cropRect;
+ }
+
+ public void setCropRect(Rect cropRect) {
+ this.cropRect = cropRect;
+ }
+
+ /**
+ * Start decoding.
+ *
+ * This must be called from the UI thread.
+ */
+ public void start() {
+ Util.validateMainThread();
+
+ thread = new HandlerThread(TAG);
+ thread.start();
+ handler = new Handler(thread.getLooper(), callback);
+ running = true;
+ requestNextPreview();
+ }
+
+
+ /**
+ * Stop decoding.
+ *
+ * This must be called from the UI thread.
+ */
+ public void stop() {
+ Util.validateMainThread();
+
+ synchronized (LOCK) {
+ running = false;
+ handler.removeCallbacksAndMessages(null);
+ thread.quit();
+ }
+ }
+
+
+ private final PreviewCallback previewCallback = new PreviewCallback() {
+ @Override
+ public void onPreview(SourceData sourceData) {
+ // TODO Auto-generated method stub
+ // Only post if running, to prevent a warning like this:
+ // java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread
+
+ // synchronize to handle cases where this is called concurrently with stop()
+ synchronized (LOCK) {
+ if (running) {
+ // Post to our thread.
+ handler.obtainMessage(R.id.zxing_decode, sourceData).sendToTarget();
+ }
+ }
+ }
+ };
+
+ private void requestNextPreview() {
+ if (cameraInstance.isOpen()) {
+ cameraInstance.requestPreview(previewCallback);
+ }
+ }
+
+ protected LuminanceSource createSource(SourceData sourceData) {
+ if (this.cropRect == null) {
+ return null;
+ } else {
+ return sourceData.createSource();
+ }
+ }
+
+ private void decode(SourceData sourceData) {
+ long start = System.currentTimeMillis();
+ Result rawResult = null;
+ sourceData.setCropRect(cropRect);
+ LuminanceSource source = createSource(sourceData);
+
+ if(source != null) {
+ rawResult = decoder.decode(source);
+ }
+
+ if (rawResult != null) {
+ // Don't log the barcode contents for security.
+ long end = System.currentTimeMillis();
+ Log.d(TAG, "Found barcode in " + (end - start) + " ms");
+ if (resultHandler != null) {
+ BarcodeResult barcodeResult = new BarcodeResult(rawResult, sourceData);
+ Message message = Message.obtain(resultHandler, R.id.zxing_decode_succeeded, barcodeResult);
+ Bundle bundle = new Bundle();
+ message.setData(bundle);
+ message.sendToTarget();
+ }
+ } else {
+ if (resultHandler != null) {
+ Message message = Message.obtain(resultHandler, R.id.zxing_decode_failed);
+ message.sendToTarget();
+ }
+ }
+ if (resultHandler != null) {
+ List resultPoints = decoder.getPossibleResultPoints();
+ Message message = Message.obtain(resultHandler, R.id.zxing_possible_result_points, resultPoints);
+ message.sendToTarget();
+ }
+ requestNextPreview();
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/DefaultDecoderFactory.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DefaultDecoderFactory.java
new file mode 100644
index 0000000..c37e6d5
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/DefaultDecoderFactory.java
@@ -0,0 +1,51 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * DecoderFactory that creates a MultiFormatReader with specified hints.
+ */
+public class DefaultDecoderFactory implements DecoderFactory {
+ private Collection decodeFormats;
+ private Map hints;
+ private String characterSet;
+
+ public DefaultDecoderFactory() {
+ }
+
+ public DefaultDecoderFactory(Collection decodeFormats, Map hints, String characterSet) {
+ this.decodeFormats = decodeFormats;
+ this.hints = hints;
+ this.characterSet = characterSet;
+ }
+
+ @Override
+ public Decoder createDecoder(Map baseHints) {
+ Map hints = new EnumMap(DecodeHintType.class);
+
+ hints.putAll(baseHints);
+
+ if(this.hints != null) {
+ hints.putAll(this.hints);
+ }
+
+ if(this.decodeFormats != null) {
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
+ }
+
+ if (characterSet != null) {
+ hints.put(DecodeHintType.CHARACTER_SET, characterSet);
+ }
+
+ MultiFormatReader reader = new MultiFormatReader();
+ reader.setHints(hints);
+
+ return new Decoder(reader);
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationCallback.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationCallback.java
new file mode 100644
index 0000000..cf3b00a
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationCallback.java
@@ -0,0 +1,13 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+/**
+ *
+ */
+public interface RotationCallback {
+ /**
+ * Rotation changed.
+ *
+ * @param rotation the current value of windowManager.getDefaultDisplay().getRotation()
+ */
+ void onRotationChanged(int rotation);
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationListener.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationListener.java
new file mode 100644
index 0000000..13449c5
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/RotationListener.java
@@ -0,0 +1,69 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.Log;
+import android.view.OrientationEventListener;
+import android.view.WindowManager;
+
+/**
+ * Hack to detect when screen rotation is reversed, since that does not cause a configuration change.
+ *
+ * If it is changed through something other than the sensor (e.g. programmatically), this may not work.
+ *
+ * See http://stackoverflow.com/q/9909037
+ */
+public class RotationListener {
+ private int lastRotation;
+
+ private WindowManager windowManager;
+ private OrientationEventListener orientationEventListener;
+ private RotationCallback callback;
+
+ public RotationListener() {
+ }
+
+
+ public void listen(Context context, RotationCallback callback) {
+ // Stop to make sure we're not registering the listening twice.
+ stop();
+
+ // Only use the ApplicationContext. In case of a memory leak (e.g. from a framework bug),
+ // this will result in less being leaked.
+ context = context.getApplicationContext();
+
+ this.callback = callback;
+
+ this.windowManager = (WindowManager) context
+ .getSystemService(Context.WINDOW_SERVICE);
+
+ this.orientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ WindowManager localWindowManager = windowManager;
+ RotationCallback localCallback = RotationListener.this.callback;
+ if(windowManager != null && localCallback != null) {
+ int newRotation = localWindowManager.getDefaultDisplay().getRotation();
+ if (newRotation != lastRotation) {
+ lastRotation = newRotation;
+ localCallback.onRotationChanged(newRotation);
+ }
+ }
+ }
+ };
+ this.orientationEventListener.enable();
+
+ lastRotation = windowManager.getDefaultDisplay().getRotation();
+ }
+
+ public void stop() {
+ // To reduce the effect of possible leaks, we clear any references we have to external
+ // objects.
+ if(this.orientationEventListener != null) {
+ this.orientationEventListener.disable();
+ }
+ this.orientationEventListener = null;
+ this.windowManager = null;
+ this.callback = null;
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/Size.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Size.java
new file mode 100644
index 0000000..952b80e
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Size.java
@@ -0,0 +1,84 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+/**
+ *
+ */
+public class Size implements Comparable {
+ public final int width;
+ public final int height;
+
+ public Size(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Swap width and height.
+ *
+ * @return a new Size with swapped width and height
+ */
+ public Size rotate() {
+ //noinspection SuspiciousNameCombination
+ return new Size(height, width);
+ }
+
+ /**
+ * Scale by n / d.
+ *
+ * @param n numerator
+ * @param d denominator
+ * @return the scaled size
+ */
+ public Size scale(int n, int d) {
+ return new Size(width * n / d, height * n / d);
+ }
+
+ /**
+ * Checks if both dimensions of the other size are at least as large as this size.
+ *
+ * @param other the size to compare with
+ * @return true if this size fits into the other size
+ */
+ public boolean fitsIn(Size other) {
+ return width <= other.width && height <= other.height;
+ }
+
+ /**
+ * Default sort order is ascending by size.
+ */
+ @Override
+ public int compareTo(Size other) {
+ int aPixels = this.height * this.width;
+ int bPixels = other.height * other.width;
+ if (bPixels < aPixels) {
+ return 1;
+ }
+ if (bPixels > aPixels) {
+ return -1;
+ }
+ return 0;
+ }
+
+ public String toString() {
+ return width + "x" + height;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Size size = (Size) o;
+
+ if (width != size.width) return false;
+ return height == size.height;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = width;
+ result = 31 * result + height;
+ return result;
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/SourceData.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/SourceData.java
new file mode 100644
index 0000000..dddb5db
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/SourceData.java
@@ -0,0 +1,233 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+
+import com.google.zxing.PlanarYUVLuminanceSource;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Raw preview data from a camera.
+ */
+public class SourceData {
+ /** Raw YUV data */
+ private byte[] data;
+
+ /** Source data width */
+ private int dataWidth;
+
+ /** Source data height */
+ private int dataHeight;
+
+ /** The format of the image data. ImageFormat.NV21 and ImageFormat.YUY2 are supported. */
+ private int imageFormat;
+
+ /** Rotation in degrees (0, 90, 180 or 270). This is camera rotation relative to display rotation. */
+ private int rotation;
+
+ /** Crop rectangle, in display orientation. */
+ private Rect cropRect;
+
+ /**
+ *
+ * @param data the image data
+ * @param dataWidth width of the data
+ * @param dataHeight height of the data
+ * @param imageFormat ImageFormat.NV21 or ImageFormat.YUY2
+ * @param rotation camera rotation relative to display rotation, in degrees (0, 90, 180 or 270).
+ */
+ public SourceData(byte[] data, int dataWidth, int dataHeight, int imageFormat, int rotation) {
+ this.data = data;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.rotation = rotation;
+ this.imageFormat = imageFormat;
+ }
+
+ public Rect getCropRect() {
+ return cropRect;
+ }
+
+ /**
+ * Set the crop rectangle.
+ *
+ * @param cropRect the new crop rectangle.
+ */
+ public void setCropRect(Rect cropRect) {
+ this.cropRect = cropRect;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ /**
+ *
+ * @return width of the data
+ */
+ public int getDataWidth() {
+ return dataWidth;
+ }
+
+ /**
+ *
+ * @return height of the data
+ */
+ public int getDataHeight() {
+ return dataHeight;
+ }
+
+ /**
+ *
+ * @return true if the preview image is rotated orthogonal to the display
+ */
+ public boolean isRotated() {
+ return rotation % 180 != 0;
+ }
+
+ public int getImageFormat() {
+ return imageFormat;
+ }
+
+ public PlanarYUVLuminanceSource createSource() {
+ byte[] rotated = rotateCameraPreview(rotation, data, dataWidth, dataHeight);
+ // TODO: handle mirrored (front) camera. Probably only the ResultPoints should be mirrored,
+ // not the preview for decoding.
+ if (isRotated()) {
+ //noinspection SuspiciousNameCombination
+ return new PlanarYUVLuminanceSource(rotated, dataHeight, dataWidth, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), false);
+ } else {
+ return new PlanarYUVLuminanceSource(rotated, dataWidth, dataHeight, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), false);
+ }
+ }
+
+ /**
+ * Return the source bitmap (cropped; in display orientation).
+ *
+ * @return the bitmap
+ */
+ public Bitmap getBitmap() {
+ return getBitmap(1);
+ }
+
+ /**
+ * Return the source bitmap (cropped; in display orientation).
+ *
+ * @param scaleFactor factor to scale down by. Must be a power of 2.
+ * @return the bitmap
+ */
+ public Bitmap getBitmap(int scaleFactor) {
+ return getBitmap(cropRect, scaleFactor);
+ }
+
+ private Bitmap getBitmap(Rect cropRect, int scaleFactor) {
+ if(isRotated()) {
+ //noinspection SuspiciousNameCombination
+ cropRect = new Rect(cropRect.top, cropRect.left, cropRect.bottom, cropRect.right);
+ }
+
+ // TODO: there should be a way to do this without JPEG compression / decompression cycle.
+ YuvImage img = new YuvImage(data, imageFormat, dataWidth, dataHeight, null);
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ img.compressToJpeg(cropRect, 90, buffer);
+ byte[] jpegData = buffer.toByteArray();
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = scaleFactor;
+ Bitmap bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
+
+ // Rotate if required
+ if (rotation != 0) {
+ Matrix imageMatrix = new Matrix();
+ imageMatrix.postRotate(rotation);
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), imageMatrix, false);
+ }
+
+ return bitmap;
+ }
+
+ public static byte[] rotateCameraPreview(int cameraRotation, byte[] data, int imageWidth, int imageHeight) {
+ switch (cameraRotation) {
+ case 0:
+ return data;
+ case 90:
+ return rotateCW(data, imageWidth, imageHeight);
+ case 180:
+ return rotate180(data, imageWidth, imageHeight);
+ case 270:
+ return rotateCCW(data, imageWidth, imageHeight);
+ default:
+ // Should not happen
+ return data;
+ }
+ }
+
+ /**
+ * Rotate an image by 90 degrees CW.
+ *
+ * @param data the image data, in with the first width * height bytes being the luminance data.
+ * @param imageWidth the width of the image
+ * @param imageHeight the height of the image
+ * @return the rotated bytes
+ */
+ public static byte[] rotateCW(byte[] data, int imageWidth, int imageHeight) {
+ // Adapted from http://stackoverflow.com/a/15775173
+ // data may contain more than just y (u and v), but we are only interested in the y section.
+
+ byte[] yuv = new byte[imageWidth * imageHeight];
+ int i = 0;
+ for (int x = 0; x < imageWidth; x++) {
+ for (int y = imageHeight - 1; y >= 0; y--) {
+ yuv[i] = data[y * imageWidth + x];
+ i++;
+ }
+ }
+ return yuv;
+ }
+
+ /**
+ * Rotate an image by 180 degrees.
+ *
+ * @param data the image data, in with the first width * height bytes being the luminance data.
+ * @param imageWidth the width of the image
+ * @param imageHeight the height of the image
+ * @return the rotated bytes
+ */
+ public static byte[] rotate180(byte[] data, int imageWidth, int imageHeight) {
+ int n = imageWidth * imageHeight;
+ byte[] yuv = new byte[n];
+
+ int i = n - 1;
+ for (int j = 0; j < n; j++) {
+ yuv[i] = data[j];
+ i--;
+ }
+ return yuv;
+ }
+
+ /**
+ * Rotate an image by 90 degrees CCW.
+ *
+ * @param data the image data, in with the first width * height bytes being the luminance data.
+ * @param imageWidth the width of the image
+ * @param imageHeight the height of the image
+ * @return the rotated bytes
+ */
+ public static byte[] rotateCCW(byte[] data, int imageWidth, int imageHeight) {
+ int n = imageWidth * imageHeight;
+ byte[] yuv = new byte[n];
+ int i = n - 1;
+ for (int x = 0; x < imageWidth; x++) {
+ for (int y = imageHeight - 1; y >= 0; y--) {
+ yuv[i] = data[y * imageWidth + x];
+ i--;
+ }
+ }
+ return yuv;
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/Util.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Util.java
new file mode 100644
index 0000000..8fed9b5
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/Util.java
@@ -0,0 +1,14 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.os.Looper;
+
+/**
+ *
+ */
+public class Util {
+ public static void validateMainThread() {
+ if (Looper.getMainLooper() != Looper.myLooper()) {
+ throw new IllegalStateException("Must be called from the main thread.");
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/ViewfinderView.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/ViewfinderView.java
new file mode 100644
index 0000000..494fa59
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/ViewfinderView.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.journeyapps.barcodescanner;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+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 com.zhongyun.viewer.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 class ViewfinderView extends View {
+ protected static final String TAG = ViewfinderView.class.getSimpleName();
+
+ protected static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
+ protected static final long ANIMATION_DELAY = 80L;
+ protected static final int CURRENT_POINT_OPACITY = 0xA0;
+ protected static final int MAX_RESULT_POINTS = 20;
+ protected static final int POINT_SIZE = 6;
+
+ protected final Paint paint;
+ protected Bitmap resultBitmap;
+ protected final int maskColor;
+ protected final int resultColor;
+ protected final int laserColor;
+ protected final int resultPointColor;
+ protected int scannerAlpha;
+ protected List possibleResultPoints;
+ protected List lastPossibleResultPoints;
+ protected CameraPreview cameraPreview;
+
+ // This constructor is used when the class is built from an XML resource.
+ public ViewfinderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Initialize these once for performance rather than calling them every time in onDraw().
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ Resources resources = getResources();
+
+ // Get setted attributes on view
+ TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_finder);
+
+ this.maskColor = attributes.getColor(R.styleable.zxing_finder_zxing_viewfinder_mask,
+ resources.getColor(R.color.zxing_viewfinder_mask));
+ this.resultColor = attributes.getColor(R.styleable.zxing_finder_zxing_result_view,
+ resources.getColor(R.color.zxing_result_view));
+ this.laserColor = attributes.getColor(R.styleable.zxing_finder_zxing_viewfinder_laser,
+ resources.getColor(R.color.zxing_viewfinder_laser));
+ this.resultPointColor = attributes.getColor(R.styleable.zxing_finder_zxing_possible_result_points,
+ resources.getColor(R.color.zxing_possible_result_points));
+
+ attributes.recycle();
+
+ scannerAlpha = 0;
+ possibleResultPoints = new ArrayList(5);
+ lastPossibleResultPoints = null;
+ }
+
+ public void setCameraPreview(CameraPreview view) {
+ this.cameraPreview = view;
+ view.addStateListener(new CameraPreview.StateListener() {
+ @Override
+ public void previewSized() {
+ invalidate();
+ }
+
+ @Override
+ public void previewStarted() {
+
+ }
+
+ @Override
+ public void previewStopped() {
+
+ }
+
+ @Override
+ public void cameraError(Exception error) {
+
+ }
+ });
+ }
+
+
+ @SuppressLint("DrawAllocation")
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (cameraPreview == null || cameraPreview.getPreviewFramingRect() == null || cameraPreview.getFramingRect() == null) {
+ return;
+ }
+
+ Rect frame = cameraPreview.getFramingRect();
+ Rect previewFrame = cameraPreview.getPreviewFramingRect();
+
+ 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 currentPossible = possibleResultPoints;
+ List 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);
+ 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);
+ 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 result An image of the result.
+ */
+ public void drawResultBitmap(Bitmap result) {
+ resultBitmap = result;
+ invalidate();
+ }
+
+ /**
+ * Only call from the UI thread.
+ *
+ * @param point a point to draw, relative to the preview frame
+ */
+ public void addPossibleResultPoint(ResultPoint point) {
+ List points = possibleResultPoints;
+ points.add(point);
+ int size = points.size();
+ if (size > MAX_RESULT_POINTS) {
+ // trim it
+ points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
+ }
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/AutoFocusManager.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/AutoFocusManager.java
new file mode 100644
index 0000000..cbecf9d
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/AutoFocusManager.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * This should be created and used from the camera thread only. The thread message queue is used
+ * to run all operations on the same thread.
+ */
+public final class AutoFocusManager {
+
+ private static final String TAG = AutoFocusManager.class.getSimpleName();
+
+ private static final long AUTO_FOCUS_INTERVAL_MS = 2000L;
+
+ private boolean stopped;
+ private boolean focusing;
+ private final boolean useAutoFocus;
+ private final Camera camera;
+ private Handler handler;
+
+ private int MESSAGE_FOCUS = 1;
+
+ private static final Collection FOCUS_MODES_CALLING_AF;
+
+ static {
+ FOCUS_MODES_CALLING_AF = new ArrayList(2);
+ FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO);
+ FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO);
+ }
+
+ private final Handler.Callback focusHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MESSAGE_FOCUS) {
+ focus();
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
+ @Override
+ public void onAutoFocus(boolean success, Camera theCamera) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ focusing = false;
+ autoFocusAgainLater();
+ }
+ });
+ }
+ };
+
+ public AutoFocusManager(Camera camera, CameraSettings settings) {
+ this.handler = new Handler(focusHandlerCallback);
+ this.camera = camera;
+ String currentFocusMode = camera.getParameters().getFocusMode();
+ useAutoFocus = settings.isAutoFocusEnabled() && FOCUS_MODES_CALLING_AF.contains(currentFocusMode);
+ Log.i(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus);
+ start();
+ }
+
+
+ private synchronized void autoFocusAgainLater() {
+ if (!stopped && !handler.hasMessages(MESSAGE_FOCUS)) {
+ handler.sendMessageDelayed(handler.obtainMessage(MESSAGE_FOCUS), AUTO_FOCUS_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Start auto-focus. The first focus will happen now, then repeated every two seconds.
+ */
+ public void start() {
+ stopped = false;
+ focus();
+ }
+
+ private void focus() {
+ if (useAutoFocus) {
+ if (!stopped && !focusing) {
+ try {
+ camera.autoFocus(autoFocusCallback);
+ focusing = true;
+ } catch (RuntimeException re) {
+ // Have heard RuntimeException reported in Android 4.0.x+; continue?
+ Log.w(TAG, "Unexpected exception while focusing", re);
+ // Try again later to keep cycle going
+ autoFocusAgainLater();
+ }
+ }
+ }
+ }
+
+ private void cancelOutstandingTask() {
+ handler.removeMessages(MESSAGE_FOCUS);
+ }
+
+ /**
+ * Stop auto-focus.
+ */
+ public void stop() {
+ stopped = true;
+ focusing = false;
+ cancelOutstandingTask();
+ if (useAutoFocus) {
+ // Doesn't hurt to call this even if not focusing
+ try {
+ camera.cancelAutoFocus();
+ } catch (RuntimeException re) {
+ // Have heard RuntimeException reported in Android 4.0.x+; continue?
+ Log.w(TAG, "Unexpected exception while cancelling focusing", re);
+ }
+ }
+ }
+
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraInstance.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraInstance.java
new file mode 100644
index 0000000..5aed9d2
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraInstance.java
@@ -0,0 +1,216 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import com.zhongyun.viewer.R;
+import com.zhongyun.zxing.journeyapps.barcodescanner.Size;
+import com.zhongyun.zxing.journeyapps.barcodescanner.Util;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+
+/**
+ *
+ */
+public class CameraInstance {
+ private static final String TAG = CameraInstance.class.getSimpleName();
+
+ private CameraThread cameraThread;
+ private SurfaceHolder surfaceHolder;
+ private CameraManager cameraManager;
+ private Handler readyHandler;
+ private DisplayConfiguration displayConfiguration;
+ private boolean open = false;
+ private CameraSettings cameraSettings = new CameraSettings();
+
+ public CameraInstance(Context context) {
+ Util.validateMainThread();
+
+ this.cameraThread = CameraThread.getInstance();
+ this.cameraManager = new CameraManager(context);
+ this.cameraManager.setCameraSettings(cameraSettings);
+ }
+
+ public void setDisplayConfiguration(DisplayConfiguration configuration) {
+ this.displayConfiguration = configuration;
+ cameraManager.setDisplayConfiguration(configuration);
+ }
+
+ public DisplayConfiguration getDisplayConfiguration() {
+ return displayConfiguration;
+ }
+
+ public void setReadyHandler(Handler readyHandler) {
+ this.readyHandler = readyHandler;
+ }
+
+ public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
+ this.surfaceHolder = surfaceHolder;
+ }
+
+ public CameraSettings getCameraSettings() {
+ return cameraSettings;
+ }
+
+ /**
+ * This only has an effect if the camera is not opened yet.
+ *
+ * @param cameraSettings the new camera settings
+ */
+ public void setCameraSettings(CameraSettings cameraSettings) {
+ if (!open) {
+ this.cameraSettings = cameraSettings;
+ this.cameraManager.setCameraSettings(cameraSettings);
+ }
+ }
+
+ /**
+ * Actual preview size in current rotation. null if not determined yet.
+ *
+ * @return preview size
+ */
+ private Size getPreviewSize() {
+ return cameraManager.getPreviewSize();
+ }
+
+ /**
+ *
+ * @return the camera rotation relative to display rotation, in degrees. Typically 0 if the
+ * display is in landscape orientation.
+ */
+ public int getCameraRotation() {
+ return cameraManager.getCameraRotation();
+ }
+
+ public void open() {
+ Util.validateMainThread();
+
+ open = true;
+
+ cameraThread.incrementAndEnqueue(opener);
+ }
+
+ public void configureCamera() {
+ Util.validateMainThread();
+ validateOpen();
+
+ cameraThread.enqueue(configure);
+ }
+
+ public void startPreview() {
+ Util.validateMainThread();
+ validateOpen();
+
+ cameraThread.enqueue(previewStarter);
+ }
+
+ public void setTorch(final boolean on) {
+ Util.validateMainThread();
+
+ if (open) {
+ cameraThread.enqueue(new Runnable() {
+ @Override
+ public void run() {
+ cameraManager.setTorch(on);
+ }
+ });
+ }
+ }
+
+ public void close() {
+ Util.validateMainThread();
+
+ if (open) {
+ cameraThread.enqueue(closer);
+ }
+
+ open = false;
+ }
+
+ public boolean isOpen() {
+ return open;
+ }
+
+ public void requestPreview(final PreviewCallback callback) {
+ validateOpen();
+
+ cameraThread.enqueue(new Runnable() {
+ @Override
+ public void run() {
+ cameraManager.requestPreviewFrame(callback);
+ }
+ });
+ }
+
+ private void validateOpen() {
+ if (!open) {
+ throw new IllegalStateException("CameraInstance is not open");
+ }
+ }
+
+
+ private Runnable opener = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Log.d(TAG, "Opening camera");
+ cameraManager.open();
+ } catch (Exception e) {
+ notifyError(e);
+ Log.e(TAG, "Failed to open camera", e);
+ }
+ }
+ };
+
+ private Runnable configure = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Log.d(TAG, "Configuring camera");
+ cameraManager.configure();
+ if (readyHandler != null) {
+ readyHandler.obtainMessage(R.id.zxing_prewiew_size_ready, getPreviewSize()).sendToTarget();
+ }
+ } catch (Exception e) {
+ notifyError(e);
+ Log.e(TAG, "Failed to configure camera", e);
+ }
+ }
+ };
+
+ private Runnable previewStarter = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Log.d(TAG, "Starting preview");
+ cameraManager.setPreviewDisplay(surfaceHolder);
+ cameraManager.startPreview();
+ } catch (Exception e) {
+ notifyError(e);
+ Log.e(TAG, "Failed to start preview", e);
+ }
+ }
+ };
+
+ private Runnable closer = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Log.d(TAG, "Closing camera");
+ cameraManager.stopPreview();
+ cameraManager.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to close camera", e);
+ }
+
+ cameraThread.decrementInstances();
+ }
+ };
+
+ private void notifyError(Exception error) {
+ if (readyHandler != null) {
+ readyHandler.obtainMessage(R.id.zxing_camera_error, error).sendToTarget();
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraManager.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraManager.java
new file mode 100644
index 0000000..d1cebbb
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraManager.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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 com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import com.zhongyun.zxing.client.android.AmbientLightManager;
+import com.zhongyun.zxing.client.android.camera.CameraConfigurationUtils;
+import com.zhongyun.zxing.client.android.camera.OpenCameraInterface;
+import com.zhongyun.zxing.journeyapps.barcodescanner.Size;
+import com.zhongyun.zxing.journeyapps.barcodescanner.SourceData;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Call order:
+ *
+ * 1. setCameraSettings()
+ * 2. open(), set desired preview size (any order)
+ * 3. configure(), setPreviewDisplay(holder) (any order)
+ * 4. startPreview()
+ * 5. requestPreviewFrame (repeat)
+ * 6. stopPreview()
+ * 7. close()
+ */
+public final class CameraManager {
+
+ private static final String TAG = CameraManager.class.getSimpleName();
+
+ private Camera camera;
+ private Camera.CameraInfo cameraInfo;
+
+ private AutoFocusManager autoFocusManager;
+ private AmbientLightManager ambientLightManager;
+
+ private boolean previewing;
+ private String defaultParameters;
+
+ // User parameters
+ private CameraSettings settings = new CameraSettings();
+
+ private DisplayConfiguration displayConfiguration;
+
+ // Actual chosen preview size
+ private Size requestedPreviewSize;
+ private Size previewSize;
+
+ private int rotationDegrees = -1; // camera rotation vs display rotation
+
+ private Context context;
+
+
+ private final class CameraPreviewCallback implements Camera.PreviewCallback {
+ private PreviewCallback callback;
+
+ private Size resolution;
+
+ public CameraPreviewCallback() {
+ }
+
+ public void setResolution(Size resolution) {
+ this.resolution = resolution;
+ }
+
+ public void setCallback(PreviewCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ Size cameraResolution = resolution;
+ PreviewCallback callback = this.callback;
+ if (cameraResolution != null && callback != null) {
+ int format = camera.getParameters().getPreviewFormat();
+ SourceData source = new SourceData(data, cameraResolution.width, cameraResolution.height, format, getCameraRotation());
+ callback.onPreview(source);
+ } else {
+ Log.d(TAG, "Got preview callback, but no handler or resolution available");
+ }
+ }
+ }
+
+ /**
+ * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+ * clear the handler so it will only receive one message.
+ */
+ private final CameraPreviewCallback cameraPreviewCallback;
+
+ public CameraManager(Context context) {
+ this.context = context;
+ cameraPreviewCallback = new CameraPreviewCallback();
+ }
+
+ /**
+ * Must be called from camera thread.
+ */
+ public void open() {
+ camera = OpenCameraInterface.open(settings.getRequestedCameraId());
+ if (camera == null) {
+ throw new RuntimeException("Failed to open camera");
+ }
+
+ int cameraId = OpenCameraInterface.getCameraId(settings.getRequestedCameraId());
+ cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(cameraId, cameraInfo);
+ }
+
+ /**
+ * Configure the camera parameters, including preview size.
+ *
+ * The camera must be opened before calling this.
+ *
+ * Must be called from camera thread.
+ */
+ public void configure() {
+ setParameters();
+ }
+
+ /**
+ * Must be called from camera thread.
+ */
+ public void setPreviewDisplay(SurfaceHolder holder) throws IOException {
+ camera.setPreviewDisplay(holder);
+ }
+
+ /**
+ * Asks the camera hardware to begin drawing preview frames to the screen.
+ *
+ * Must be called from camera thread.
+ */
+ public void startPreview() {
+ Camera theCamera = camera;
+ if (theCamera != null && !previewing) {
+ theCamera.startPreview();
+ previewing = true;
+ autoFocusManager = new AutoFocusManager(camera, settings);
+ ambientLightManager = new AmbientLightManager(context, this, settings);
+ ambientLightManager.start();
+ }
+ }
+
+ /**
+ * Tells the camera to stop drawing preview frames.
+ *
+ * Must be called from camera thread.
+ */
+ public void stopPreview() {
+ if (autoFocusManager != null) {
+ autoFocusManager.stop();
+ autoFocusManager = null;
+ }
+ if (ambientLightManager != null) {
+ ambientLightManager.stop();
+ ambientLightManager = null;
+ }
+ if (camera != null && previewing) {
+ camera.stopPreview();
+ cameraPreviewCallback.setCallback(null);
+ previewing = false;
+ }
+ }
+
+
+ /**
+ * Closes the camera driver if still in use.
+ *
+ * Must be called from camera thread.
+ */
+ public void close() {
+ if (camera != null) {
+ camera.release();
+ camera = null;
+ }
+ }
+
+ /**
+ * @return true if the camera rotation is perpendicular to the current display rotation.
+ */
+ public boolean isCameraRotated() {
+ if(rotationDegrees == -1) {
+ throw new IllegalStateException("Rotation not calculated yet. Call configure() first.");
+ }
+ return rotationDegrees % 180 != 0;
+ }
+
+ /**
+ *
+ * @return the camera rotation relative to display rotation, in degrees. Typically 0 if the
+ * display is in landscape orientation.
+ */
+ public int getCameraRotation() {
+ return rotationDegrees;
+ }
+
+
+ private Camera.Parameters getDefaultCameraParameters() {
+ Camera.Parameters parameters = camera.getParameters();
+ if (defaultParameters == null) {
+ defaultParameters = parameters.flatten();
+ } else {
+ parameters.unflatten(defaultParameters);
+ }
+ return parameters;
+ }
+
+ private void setDesiredParameters(boolean safeMode) {
+ Camera.Parameters parameters = getDefaultCameraParameters();
+
+ //noinspection ConstantConditions
+ if (parameters == null) {
+ Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration.");
+ return;
+ }
+
+ Log.i(TAG, "Initial camera parameters: " + parameters.flatten());
+
+ if (safeMode) {
+ Log.w(TAG, "In camera config safe mode -- most settings will not be honored");
+ }
+
+
+ CameraConfigurationUtils.setFocus(parameters, settings.isAutoFocusEnabled(), !settings.isContinuousFocusEnabled(), safeMode);
+
+ if (!safeMode) {
+ CameraConfigurationUtils.setTorch(parameters, false);
+
+ if (settings.isScanInverted()) {
+ CameraConfigurationUtils.setInvertColor(parameters);
+ }
+
+ if (settings.isBarcodeSceneModeEnabled()) {
+ CameraConfigurationUtils.setBarcodeSceneMode(parameters);
+ }
+
+ if (settings.isMeteringEnabled()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ CameraConfigurationUtils.setVideoStabilization(parameters);
+ CameraConfigurationUtils.setFocusArea(parameters);
+ CameraConfigurationUtils.setMetering(parameters);
+ }
+ }
+
+ }
+
+ List previewSizes = getPreviewSizes(parameters);
+ if (previewSizes.size() == 0) {
+ requestedPreviewSize = null;
+ } else {
+ requestedPreviewSize = displayConfiguration.getBestPreviewSize(previewSizes, isCameraRotated());
+
+ parameters.setPreviewSize(requestedPreviewSize.width, requestedPreviewSize.height);
+ }
+
+ if (Build.DEVICE.equals("glass-1")) {
+ // We need to set the FPS on Google Glass devices, otherwise the preview is scrambled.
+ // FIXME - can/should we do this for other devices as well?
+ CameraConfigurationUtils.setBestPreviewFPS(parameters);
+ }
+
+ Log.i(TAG, "Final camera parameters: " + parameters.flatten());
+
+ camera.setParameters(parameters);
+ }
+
+ private static List getPreviewSizes(Camera.Parameters parameters) {
+ List rawSupportedSizes = parameters.getSupportedPreviewSizes();
+ List previewSizes = new ArrayList();
+ if (rawSupportedSizes == null) {
+ Camera.Size defaultSize = parameters.getPreviewSize();
+ if (defaultSize != null) {
+ // Work around potential platform bugs
+ previewSizes.add(new Size(defaultSize.width, defaultSize.height));
+ }
+ return previewSizes;
+ }
+ for (Camera.Size size : rawSupportedSizes) {
+ previewSizes.add(new Size(size.width, size.height));
+ }
+ return previewSizes;
+ }
+
+ private int calculateDisplayRotation() {
+ // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
+ int rotation = displayConfiguration.getRotation();
+ int degrees = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break;
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ }
+
+ int result;
+ if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ result = (cameraInfo.orientation + degrees) % 360;
+ result = (360 - result) % 360; // compensate the mirror
+ } else { // back-facing
+ result = (cameraInfo.orientation - degrees + 360) % 360;
+ }
+ Log.i(TAG, "Camera Display Orientation: " + result);
+ return result;
+ }
+
+ private void setCameraDisplayOrientation(int rotation) {
+ camera.setDisplayOrientation(rotation);
+ }
+
+
+ private void setParameters() {
+ try {
+ this.rotationDegrees = calculateDisplayRotation();
+ setCameraDisplayOrientation(rotationDegrees);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to set rotation.");
+ }
+ try {
+ setDesiredParameters(false);
+ } catch (Exception e) {
+ // Failed, use safe mode
+ try {
+ setDesiredParameters(false);
+ } catch (Exception e2) {
+ // Well, darn. Give up
+ Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
+ }
+ }
+
+ Camera.Size realPreviewSize = camera.getParameters().getPreviewSize();
+ if (realPreviewSize == null) {
+ previewSize = requestedPreviewSize;
+ } else {
+ previewSize = new Size(realPreviewSize.width, realPreviewSize.height);
+ }
+ cameraPreviewCallback.setResolution(previewSize);
+ }
+
+
+ public boolean isOpen() {
+ return camera != null;
+ }
+
+ /**
+ * Actual preview size in *natural camera* orientation. null if not determined yet.
+ *
+ * @return preview size
+ */
+ public Size getNaturalPreviewSize() {
+ return previewSize;
+ }
+
+ /**
+ * Actual preview size in *current display* rotation. null if not determined yet.
+ *
+ * @return preview size
+ */
+ public Size getPreviewSize() {
+ if (previewSize == null) {
+ return null;
+ } else if (this.isCameraRotated()) {
+ return previewSize.rotate();
+ } else {
+ return previewSize;
+ }
+ }
+
+ /**
+ * A single preview frame will be returned to the supplied callback.
+ *
+ * The thread on which this called is undefined, so a Handler should be used to post the result
+ * to the correct thread.
+ *
+ * @param callback The callback to receive the preview.
+ */
+ public void requestPreviewFrame(PreviewCallback callback) {
+ Camera theCamera = camera;
+ if (theCamera != null && previewing) {
+ cameraPreviewCallback.setCallback(callback);
+ theCamera.setOneShotPreviewCallback(cameraPreviewCallback);
+ }
+ }
+
+ public CameraSettings getCameraSettings() {
+ return settings;
+ }
+
+ public void setCameraSettings(CameraSettings settings) {
+ this.settings = settings;
+ }
+
+ public DisplayConfiguration getDisplayConfiguration() {
+ return displayConfiguration;
+ }
+
+ public void setDisplayConfiguration(DisplayConfiguration displayConfiguration) {
+ this.displayConfiguration = displayConfiguration;
+ }
+
+ public void setTorch(boolean on) {
+ if (camera != null) {
+ boolean isOn = isTorchOn();
+ if (on != isOn) {
+ if (autoFocusManager != null) {
+ autoFocusManager.stop();
+ }
+
+ Camera.Parameters parameters = camera.getParameters();
+ CameraConfigurationUtils.setTorch(parameters, on);
+ if (settings.isExposureEnabled()) {
+ CameraConfigurationUtils.setBestExposure(parameters, on);
+ }
+ camera.setParameters(parameters);
+
+ if (autoFocusManager != null) {
+ autoFocusManager.start();
+ }
+ }
+ }
+ }
+
+ public boolean isTorchOn() {
+ Camera.Parameters parameters = camera.getParameters();
+ if (parameters != null) {
+ String flashMode = parameters.getFlashMode();
+ return flashMode != null &&
+ (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) ||
+ Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraSettings.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraSettings.java
new file mode 100644
index 0000000..f6b2ded
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraSettings.java
@@ -0,0 +1,129 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import com.zhongyun.zxing.client.android.camera.OpenCameraInterface;
+
+
+/**
+ *
+ */
+public class CameraSettings {
+ private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
+ private boolean scanInverted = false;
+ private boolean barcodeSceneModeEnabled = false;
+ private boolean meteringEnabled = false;
+ private boolean autoFocusEnabled = true;
+ private boolean continuousFocusEnabled = false;
+ private boolean exposureEnabled = false;
+ private boolean autoTorchEnabled = false;
+
+
+ public int getRequestedCameraId() {
+ return requestedCameraId;
+ }
+
+
+ /**
+ * Allows third party apps to specify the camera ID, rather than determine
+ * it automatically based on available cameras and their orientation.
+ *
+ * @param requestedCameraId camera ID of the camera to use. A negative value means "no preference".
+ */
+ public void setRequestedCameraId(int requestedCameraId) {
+ this.requestedCameraId = requestedCameraId;
+ }
+
+ /**
+ * Default to false.
+ *
+ * Inverted means dark & light colors are inverted.
+ *
+ * @return true if scan is inverted
+ */
+ public boolean isScanInverted() {
+ return scanInverted;
+ }
+
+ public void setScanInverted(boolean scanInverted) {
+ this.scanInverted = scanInverted;
+ }
+
+ /**
+ * Default to false.
+ *
+ * @return true if barcode scene mode is enabled
+ */
+ public boolean isBarcodeSceneModeEnabled() {
+ return barcodeSceneModeEnabled;
+ }
+
+ public void setBarcodeSceneModeEnabled(boolean barcodeSceneModeEnabled) {
+ this.barcodeSceneModeEnabled = barcodeSceneModeEnabled;
+ }
+
+ /**
+ * Default to false.
+ *
+ * @return true if exposure is enabled.
+ */
+ public boolean isExposureEnabled() {
+ return exposureEnabled;
+ }
+
+ public void setExposureEnabled(boolean exposureEnabled) {
+ this.exposureEnabled = exposureEnabled;
+ }
+
+ /**
+ * Default to false.
+ *
+ * If enabled, metering is performed to determine focus area.
+ *
+ * @return true if metering is enabled
+ */
+ public boolean isMeteringEnabled() {
+ return meteringEnabled;
+ }
+
+ public void setMeteringEnabled(boolean meteringEnabled) {
+ this.meteringEnabled = meteringEnabled;
+ }
+
+ /**
+ * Default to true.
+ *
+ * @return true if auto-focus is enabled
+ */
+ public boolean isAutoFocusEnabled() {
+ return autoFocusEnabled;
+ }
+
+ public void setAutoFocusEnabled(boolean autoFocusEnabled) {
+ this.autoFocusEnabled = autoFocusEnabled;
+ }
+
+ /**
+ * Default to false.
+ *
+ * @return true if continuous focus is enabled
+ */
+ public boolean isContinuousFocusEnabled() {
+ return continuousFocusEnabled;
+ }
+
+ public void setContinuousFocusEnabled(boolean continuousFocusEnabled) {
+ this.continuousFocusEnabled = continuousFocusEnabled;
+ }
+
+ /**
+ * Default to false.
+ *
+ * @return true if the torch is automatically controlled based on ambient light.
+ */
+ public boolean isAutoTorchEnabled() {
+ return autoTorchEnabled;
+ }
+
+ public void setAutoTorchEnabled(boolean autoTorchEnabled) {
+ this.autoTorchEnabled = autoTorchEnabled;
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraThread.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraThread.java
new file mode 100644
index 0000000..8b1d4fe
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/CameraThread.java
@@ -0,0 +1,90 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Singleton thread that is started and stopped on demand.
+ *
+ * Any access to Camera / CameraManager should happen on this thread, through CameraInstance.
+ */
+class CameraThread {
+ private static final String TAG = CameraThread.class.getSimpleName();
+
+ private static CameraThread instance;
+
+ public static CameraThread getInstance() {
+ if (instance == null) {
+ instance = new CameraThread();
+ }
+ return instance;
+ }
+
+
+ private Handler handler;
+ private HandlerThread thread;
+
+ private int openCount = 0;
+
+ private final Object LOCK = new Object();
+
+
+ private CameraThread() {
+ }
+
+ /**
+ * Call from main thread.
+ *
+ * Enqueues a task on the camera thread.
+ *
+ * @param runnable the task to enqueue
+ */
+ protected void enqueue(Runnable runnable) {
+ synchronized (LOCK) {
+ if (this.handler == null) {
+ if (openCount <= 0) {
+ throw new IllegalStateException("CameraThread is not open");
+ }
+ this.thread = new HandlerThread("CameraThread");
+ this.thread.start();
+ this.handler = new Handler(thread.getLooper());
+ }
+ this.handler.post(runnable);
+ }
+ }
+
+ /**
+ * Call from camera thread.
+ */
+ private void quit() {
+ synchronized (LOCK) {
+ this.thread.quit();
+ this.thread = null;
+ this.handler = null;
+ }
+ }
+
+ /**
+ * Call from camera thread
+ */
+ protected void decrementInstances() {
+ synchronized (LOCK) {
+ openCount -= 1;
+ if (openCount == 0) {
+ quit();
+ }
+ }
+ }
+
+ /**
+ * Call from main thread.
+ *
+ * @param runner
+ */
+ protected void incrementAndEnqueue(Runnable runner) {
+ synchronized (LOCK) {
+ openCount += 1;
+ enqueue(runner);
+ }
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/DisplayConfiguration.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/DisplayConfiguration.java
new file mode 100644
index 0000000..fb6d2b0
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/DisplayConfiguration.java
@@ -0,0 +1,195 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.Surface;
+
+
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.zhongyun.zxing.journeyapps.barcodescanner.Size;
+
+/**
+ *
+ */
+public class DisplayConfiguration {
+ private static final String TAG = DisplayConfiguration.class.getSimpleName();
+
+ private Size viewfinderSize;
+ private int rotation;
+ private boolean center = false;
+
+ public DisplayConfiguration(int rotation) {
+ this.rotation = rotation;
+ }
+
+ public DisplayConfiguration(int rotation, Size viewfinderSize) {
+ this.rotation = rotation;
+ this.viewfinderSize = viewfinderSize;
+ }
+
+ public int getRotation() {
+ return rotation;
+ }
+
+ public Size getViewfinderSize() {
+ return viewfinderSize;
+ }
+
+ /**
+ * @param rotate true to rotate the preview size
+ * @return desired preview size in natural camera orientation.
+ */
+ public Size getDesiredPreviewSize(boolean rotate) {
+ if (viewfinderSize == null) {
+ return null;
+ } else if (rotate) {
+ return viewfinderSize.rotate();
+ } else {
+ return viewfinderSize;
+ }
+ }
+
+ /**
+ * Choose the best preview size, based on our display size.
+ *
+ * We prefer:
+ * 1. no scaling
+ * 2. least downscaling
+ * 3. least upscaling
+ *
+ * We do not care much about aspect ratio, since we just crop away extra pixels. We only choose
+ * the size to minimize scaling.
+ *
+ * In the future we may consider choosing the biggest possible preview size, to maximize the
+ * resolution we have for decoding. We need more testing to see whether or not that is feasible.
+ *
+ * @param sizes supported preview sizes, containing at least one size. Sizes are in natural camera orientation.
+ * @param isRotated true if the camera is rotated perpendicular to the current display orientation
+ * @return the best preview size, never null
+ */
+ public Size getBestPreviewSize(List sizes, boolean isRotated) {
+ // Sample of supported preview sizes:
+ // http://www.kirill.org/ar/ar.php
+
+
+ final Size desired = getDesiredPreviewSize(isRotated);
+
+ if (desired == null) {
+ return sizes.get(0);
+ }
+
+ Collections.sort(sizes, new Comparator() {
+ @Override
+ public int compare(Size a, Size b) {
+ Size ascaled = scale(a, desired);
+ int aScale = ascaled.width - a.width;
+ Size bscaled = scale(b, desired);
+ int bScale = bscaled.width - b.width;
+
+ if (aScale == 0 && bScale == 0) {
+ // Both no scaling, pick the smaller one
+ return a.compareTo(b);
+ } else if (aScale == 0) {
+ // No scaling for a; pick a
+ return -1;
+ } else if (bScale == 0) {
+ // No scaling for b; pick b
+ return 1;
+ } else if (aScale < 0 && bScale < 0) {
+ // Both downscaled. Pick the smaller one (less downscaling).
+ return a.compareTo(b);
+ } else if (aScale > 0 && bScale > 0) {
+ // Both upscaled. Pick the larger one (less upscaling).
+ return -a.compareTo(b);
+ } else if (aScale < 0) {
+ // a downscaled, b upscaled. Pick a.
+ return -1;
+ } else {
+ // a upscaled, b downscaled. Pick b.
+ return 1;
+ }
+ }
+ });
+
+ Log.i(TAG, "Viewfinder size: " + desired);
+ Log.i(TAG, "Preview in order of preference: " + sizes);
+
+ return sizes.get(0);
+ }
+
+ /**
+ * Scale from so that to.fitsIn(size). Tries to scale by powers of two, or by 3/2. Aspect ratio
+ * is preserved.
+ *
+ * These scaling factors will theoretically result in fast scaling with minimal quality loss.
+ *
+ * TODO: confirm whether or not this is the case in practice.
+ *
+ * @param from the start size
+ * @param to the minimum desired size
+ * @return the scaled size
+ */
+ public static Size scale(Size from, Size to) {
+ Size current = from;
+
+ if (!to.fitsIn(current)) {
+ // Scale up
+ while (true) {
+ Size scaled150 = current.scale(3, 2);
+ Size scaled200 = current.scale(2, 1);
+ if (to.fitsIn(scaled150)) {
+ // Scale by 3/2
+ return scaled150;
+ } else if (to.fitsIn(scaled200)) {
+ // Scale by 2/1
+ return scaled200;
+ } else {
+ // Scale by 2/1 and continue
+ current = scaled200;
+ }
+ }
+ } else {
+ // Scale down
+ while (true) {
+ Size scaled66 = current.scale(2, 3);
+ Size scaled50 = current.scale(1, 2);
+
+ if (!to.fitsIn(scaled50)) {
+ if (to.fitsIn(scaled66)) {
+ // Scale by 2/3
+ return scaled66;
+ } else {
+ // No more downscaling
+ return current;
+ }
+ } else {
+ // Scale by 1/2
+ current = scaled50;
+ }
+ }
+ }
+ }
+
+ /**
+ * Scale the preview to cover the viewfinder, then center it.
+ *
+ * Aspect ratio is preserved.
+ *
+ * @param previewSize the size of the preview
+ * @return a rect placing the preview
+ */
+ public Rect scalePreview(Size previewSize) {
+ // We avoid scaling if feasible.
+ Size scaledPreview = scale(previewSize, viewfinderSize);
+ Log.i(TAG, "Preview: " + previewSize + "; Scaled: " + scaledPreview + "; Want: " + viewfinderSize);
+
+ int dx = (scaledPreview.width - viewfinderSize.width) / 2;
+ int dy = (scaledPreview.height - viewfinderSize.height) / 2;
+
+ return new Rect(-dx, -dy, scaledPreview.width - dx, scaledPreview.height - dy);
+ }
+}
diff --git a/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/PreviewCallback.java b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/PreviewCallback.java
new file mode 100644
index 0000000..3fb8ea8
--- /dev/null
+++ b/src/com/zhongyun/zxing/journeyapps/barcodescanner/camera/PreviewCallback.java
@@ -0,0 +1,11 @@
+package com.zhongyun.zxing.journeyapps.barcodescanner.camera;
+
+import com.zhongyun.zxing.journeyapps.barcodescanner.SourceData;
+
+
+/**
+ * Callback for camera previews.
+ */
+public interface PreviewCallback {
+ void onPreview(SourceData sourceData);
+}