diff --git a/android-m3/AndroidManifest.xml b/android-m3/AndroidManifest.xml
new file mode 100644
index 000000000..8f925eb85
--- /dev/null
+++ b/android-m3/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android-m3/build.xml b/android-m3/build.xml
new file mode 100644
index 000000000..57b6d5379
--- /dev/null
+++ b/android-m3/build.xml
@@ -0,0 +1,265 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Generating R.java...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Packaging resources and assets...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Packaging resources...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Packaging java...
+
+
+
+
+
+ Packaging dex...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-m3/res/drawable/icon.png b/android-m3/res/drawable/icon.png
new file mode 100644
index 000000000..ee33194c0
Binary files /dev/null and b/android-m3/res/drawable/icon.png differ
diff --git a/android-m3/res/layout/main.xml b/android-m3/res/layout/main.xml
new file mode 100644
index 000000000..a841f282c
--- /dev/null
+++ b/android-m3/res/layout/main.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/android-m3/res/values/ids.xml b/android-m3/res/values/ids.xml
new file mode 100644
index 000000000..776e72925
--- /dev/null
+++ b/android-m3/res/values/ids.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/android-m3/src/com/google/zxing/client/android/AndroidGraphicsGridSampler.java b/android-m3/src/com/google/zxing/client/android/AndroidGraphicsGridSampler.java
new file mode 100755
index 000000000..9aa3f78af
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/AndroidGraphicsGridSampler.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.graphics.Matrix;
+import com.google.zxing.MonochromeBitmapSource;
+import com.google.zxing.ReaderException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.GridSampler;
+
+/**
+ * Implementation based on Android's
+ * {@link Matrix#setPolyToPoly(float[], int, float[], int, int)}
+ * class, which should offer faster performance for these matrix
+ * operations.
+ *
+ * @author srowen@google.com (Sean Owen)
+ */
+public final class AndroidGraphicsGridSampler extends GridSampler {
+
+ @Override
+ public BitMatrix sampleGrid(MonochromeBitmapSource image,
+ int dimension,
+ float p1ToX, float p1ToY,
+ float p2ToX, float p2ToY,
+ float p3ToX, float p3ToY,
+ float p4ToX, float p4ToY,
+ float p1FromX, float p1FromY,
+ float p2FromX, float p2FromY,
+ float p3FromX, float p3FromY,
+ float p4FromX, float p4FromY) throws ReaderException {
+
+ Matrix transformMatrix = new Matrix();
+ boolean succeeded = transformMatrix.setPolyToPoly(
+ new float[] { p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY },
+ 0,
+ new float[] { p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY },
+ 0,
+ 4
+ );
+ if (!succeeded) {
+ throw new ReaderException("Could not establish transformation matrix");
+ }
+
+ BitMatrix bits = new BitMatrix(dimension);
+ float[] points = new float[dimension << 1];
+ for (int i = 0; i < dimension; i++) {
+ int max = points.length;
+ float iValue = (float) i + 0.5f;
+ for (int j = 0; j < max; j += 2) {
+ points[j] = (float) (j >> 1) + 0.5f;
+ points[j + 1] = iValue;
+ }
+ transformMatrix.mapPoints(points);
+ // Quick check to see if points transformed to something inside the image;
+ // sufficent to check the endpoints
+ checkAndNudgePoints(image, points);
+ for (int j = 0; j < max; j += 2) {
+ if (image.isBlack((int) points[j], (int) points[j + 1])) {
+ // Black(-ish) pixel
+ bits.set(i, j >> 1);
+ }
+ }
+ }
+ return bits;
+ }
+
+}
diff --git a/android-m3/src/com/google/zxing/client/android/AndroidIntentParsedResult.java b/android-m3/src/com/google/zxing/client/android/AndroidIntentParsedResult.java
new file mode 100644
index 000000000..471410c82
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/AndroidIntentParsedResult.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.content.Intent;
+import com.google.zxing.client.result.ParsedReaderResult;
+import com.google.zxing.client.result.ParsedReaderResultType;
+
+import java.net.URISyntaxException;
+
+/**
+ * A {@link ParsedReaderResult} derived from a URI that encodes an Android
+ * {@link Intent}, and which should presumably trigger that intent on Android.
+ *
+ * @author srowen@google.com (Sean Owen)
+ */
+public final class AndroidIntentParsedResult extends ParsedReaderResult {
+
+ private final Intent intent;
+
+ private AndroidIntentParsedResult(Intent intent) {
+ super(ParsedReaderResultType.ANDROID_INTENT);
+ this.intent = intent;
+ }
+
+ public static AndroidIntentParsedResult parse(String rawText) {
+ try {
+ return new AndroidIntentParsedResult(Intent.getIntent(rawText));
+ } catch (URISyntaxException urise) {
+ return null;
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ }
+
+ public Intent getIntent() {
+ return intent;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return intent.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java b/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java
new file mode 100644
index 000000000..31966552d
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.result.ParsedReaderResult;
+import com.google.zxing.client.result.ParsedReaderResultType;
+
+/**
+ * The barcode reader activity itself. This is loosely based on the CameraPreview
+ * example included in the Android SDK.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Android Team (for CameraPreview example)
+ */
+public final class BarcodeReaderCaptureActivity extends Activity {
+
+ private CameraManager cameraManager;
+ private CameraSurfaceView surfaceView;
+ private WorkerThread workerThread;
+
+ private static final int ABOUT_ID = Menu.FIRST;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ // Make sure to create a TRANSLUCENT window. This is required for SurfaceView to work.
+ // Eventually this'll be done by the system automatically.
+ getWindow().setAttributes(new LayoutParams(LayoutParams.APPLICATION_TYPE,
+ LayoutParams.NO_STATUS_BAR_FLAG));
+ getWindow().setFormat(PixelFormat.TRANSLUCENT);
+
+ cameraManager = new CameraManager(getApplication());
+ surfaceView = new CameraSurfaceView(getApplication(), cameraManager);
+ setContentView(surfaceView);
+ workerThread = new WorkerThread(surfaceView, cameraManager, messageHandler);
+ workerThread.requestPreviewLoop();
+ workerThread.start();
+
+ // TODO re-enable this when issues with Matrix.setPolyToPoly() are resolved
+ //GridSampler.setGridSampler(new AndroidGraphicsGridSampler());
+ }
+
+ @Override
+ protected boolean isFullscreenOpaque() {
+ // Our main window is set to translucent, but we know that we will
+ // fill it with opaque data. Tell the system that so it can perform
+ // some important optimizations.
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ cameraManager.openDriver();
+ if (workerThread == null) {
+ workerThread = new WorkerThread(surfaceView, cameraManager, messageHandler);
+ workerThread.requestPreviewLoop();
+ workerThread.start();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (workerThread != null) {
+ workerThread.requestExitAndWait();
+ workerThread = null;
+ }
+ cameraManager.closeDriver();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ workerThread.requestStillAndDecode();
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, ABOUT_ID, R.string.menu_about);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(Menu.Item item) {
+ switch (item.getId()) {
+ case ABOUT_ID:
+ Context context = getApplication();
+ showAlert(context.getString(R.string.title_about),
+ context.getString(R.string.msg_about),
+ context.getString(R.string.button_ok), null, true, null);
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private final Handler messageHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case R.id.decoding_succeeded_message:
+ handleDecode((Result) message.obj);
+ break;
+ case R.id.decoding_failed_message:
+ Context context = getApplication();
+ showAlert(context.getString(R.string.title_no_barcode_detected),
+ context.getString(R.string.msg_no_barcode_detected),
+ context.getString(R.string.button_ok), null, true, null);
+ break;
+ }
+ }
+ };
+
+ public void restartPreview() {
+ workerThread.requestPreviewLoop();
+ }
+
+ // TODO(dswitkin): These deprecated showAlert calls need to be updated.
+ private void handleDecode(Result rawResult) {
+ ResultPoint[] points = rawResult.getResultPoints();
+ if (points != null && points.length > 0) {
+ surfaceView.drawResultPoints(points);
+ }
+
+ Context context = getApplication();
+ ParsedReaderResult readerResult = parseReaderResult(rawResult);
+ ResultHandler handler = new ResultHandler(this, readerResult);
+ if (handler.getIntent() != null) {
+ // Can be handled by some external app; ask if the user wants to
+ // proceed first though
+ Message yesMessage = handler.obtainMessage(R.string.button_yes);
+ Message noMessage = handler.obtainMessage(R.string.button_no);
+ showAlert(context.getString(getDialogTitleID(readerResult.getType())),
+ readerResult.getDisplayResult(), context.getString(R.string.button_yes),
+ yesMessage, context.getString(R.string.button_no), noMessage, true, noMessage);
+ } else {
+ // Just show information to user
+ Message okMessage = handler.obtainMessage(R.string.button_ok);
+ showAlert(context.getString(R.string.title_barcode_detected),
+ readerResult.getDisplayResult(), context.getString(R.string.button_ok), okMessage, null,
+ null, true, okMessage);
+ }
+ }
+
+ private static ParsedReaderResult parseReaderResult(Result rawResult) {
+ ParsedReaderResult readerResult = ParsedReaderResult.parseReaderResult(rawResult);
+ if (readerResult.getType().equals(ParsedReaderResultType.TEXT)) {
+ String rawText = rawResult.getText();
+ AndroidIntentParsedResult androidResult = AndroidIntentParsedResult.parse(rawText);
+ if (androidResult != null) {
+ Intent intent = androidResult.getIntent();
+ if (!Intent.VIEW_ACTION.equals(intent.getAction())) {
+ // For now, don't take anything that just parses as a View action. A lot
+ // of things are accepted as a View action by default.
+ readerResult = androidResult;
+ }
+ }
+ }
+ return readerResult;
+ }
+
+ private static int getDialogTitleID(ParsedReaderResultType type) {
+ if (type.equals(ParsedReaderResultType.ADDRESSBOOK)) {
+ return R.string.title_add_contact;
+ } else if (type.equals(ParsedReaderResultType.URI) ||
+ type.equals(ParsedReaderResultType.BOOKMARK) ||
+ type.equals(ParsedReaderResultType.URLTO)) {
+ return R.string.title_open_url;
+ } else if (type.equals(ParsedReaderResultType.EMAIL) ||
+ type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) {
+ return R.string.title_compose_email;
+ } else if (type.equals(ParsedReaderResultType.UPC)) {
+ return R.string.title_lookup_barcode;
+ } else if (type.equals(ParsedReaderResultType.TEL)) {
+ return R.string.title_dial;
+ } else if (type.equals(ParsedReaderResultType.GEO)) {
+ return R.string.title_view_maps;
+ } else {
+ return R.string.title_barcode_detected;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/android-m3/src/com/google/zxing/client/android/CameraManager.java b/android-m3/src/com/google/zxing/client/android/CameraManager.java
new file mode 100644
index 000000000..0cd3782f6
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/CameraManager.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.CameraDevice;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+import com.google.zxing.ResultPoint;
+
+/**
+ * This object wraps the CameraDevice and expects to be the only one talking to it. The
+ * implementation encapsulates the steps needed to take preview-sized images and well as high
+ * resolution stills.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class CameraManager {
+
+ private static final String TAG = "CameraManager";
+
+ private final Context context;
+ private Point cameraResolution;
+ private Point stillResolution;
+ private int stillMultiplier;
+ private Point screenResolution;
+ private Rect framingRect;
+ private final Bitmap bitmap;
+ private CameraDevice camera;
+ private final CameraDevice.CaptureParams params;
+ private boolean previewMode;
+
+ CameraManager(Context context) {
+ this.context = context;
+ calculateStillResolution();
+ getScreenResolution();
+ bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false);
+ camera = CameraDevice.open();
+ params = new CameraDevice.CaptureParams();
+ previewMode = false;
+ setPreviewMode(true);
+ }
+
+ public void openDriver() {
+ if (camera == null) {
+ camera = CameraDevice.open();
+ }
+ }
+
+ public void closeDriver() {
+ if (camera != null) {
+ camera.close();
+ camera = null;
+ }
+ }
+
+ public void capturePreview(Canvas canvas) {
+ setPreviewMode(true);
+ camera.capture(canvas);
+ }
+
+ public Bitmap captureStill() {
+ setPreviewMode(false);
+ Canvas canvas = new Canvas(bitmap);
+ camera.capture(canvas);
+ return bitmap;
+ }
+
+ /**
+ * Calculates the framing rect which the UI should draw to show the user where to place the
+ * barcode. The actual captured image should be a bit larger than indicated because they might
+ * frame the shot too tightly. This target helps with alignment as well as forces the user to hold
+ * the device far enough away to ensure the image will be in focus.
+ *
+ * @return The rectangle to draw on screen in window coordinates.
+ */
+ public Rect getFramingRect() {
+ if (framingRect == null) {
+ int size = stillResolution.x * screenResolution.x / cameraResolution.x;
+ int leftOffset = (screenResolution.x - size) / 2;
+ int topOffset = (screenResolution.y - size) / 2;
+ framingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
+ }
+ return framingRect;
+ }
+
+ /**
+ * Converts the result points from still resolution coordinates to screen coordinates.
+ *
+ * @param points The points returned by the Reader subclass through Result.getResultPoints().
+ * @return An array of Points scaled to the size of the framing rect and offset appropriately
+ * so they can be drawn in screen coordinates.
+ */
+ public Point[] convertResultPoints(ResultPoint[] points) {
+ Rect frame = getFramingRect();
+ int frameSize = frame.width();
+ int count = points.length;
+ Point[] output = new Point[count];
+ for (int x = 0; x < count; x++) {
+ output[x] = new Point();
+ output[x].x = frame.left + (int) (points[x].getX() * frameSize / stillResolution.x + 0.5f);
+ output[x].y = frame.top + (int) (points[x].getY() * frameSize / stillResolution.y + 0.5f);
+ }
+ return output;
+ }
+
+ /**
+ * Images for the live preview are taken at low resolution in RGB. The final stills for the
+ * decoding step are taken in YUV, since we only need the luminance channel. Other code depends
+ * on the ability to call this method for free if the correct mode is already set.
+ *
+ * @param on Setting on true will engage preview mode, setting it false will request still mode.
+ */
+ private void setPreviewMode(boolean on) {
+ if (on != previewMode) {
+ if (on) {
+ params.type = 1; // preview
+ if (cameraResolution.x / (float) cameraResolution.y <
+ screenResolution.x / (float) screenResolution.y) {
+ params.srcWidth = cameraResolution.x;
+ params.srcHeight = cameraResolution.x * screenResolution.y / screenResolution.x;
+ params.leftPixel = 0;
+ params.topPixel = (cameraResolution.y - params.srcHeight) / 2;
+ } else {
+ params.srcWidth = cameraResolution.y * screenResolution.x / screenResolution.y;
+ params.srcHeight = cameraResolution.y;
+ params.leftPixel = (cameraResolution.x - params.srcWidth) / 2;
+ params.topPixel = 0;
+ }
+ params.outputWidth = screenResolution.x;
+ params.outputHeight = screenResolution.y;
+ params.dataFormat = 2; // RGB565
+ } else {
+ params.type = 0; // still
+ params.srcWidth = stillResolution.x * stillMultiplier;
+ params.srcHeight = stillResolution.y * stillMultiplier;
+ params.leftPixel = (cameraResolution.x - params.srcWidth) / 2;
+ params.topPixel = (cameraResolution.y - params.srcHeight) / 2;
+ params.outputWidth = stillResolution.x;
+ params.outputHeight = stillResolution.y;
+ params.dataFormat = 2; // RGB565
+ }
+ String captureType = on ? "preview" : "still";
+ Log.v(TAG, "Setting params for " + captureType + ": srcWidth " + params.srcWidth +
+ " srcHeight " + params.srcHeight + " leftPixel " + params.leftPixel + " topPixel " +
+ params.topPixel + " outputWidth " + params.outputWidth + " outputHeight " +
+ params.outputHeight);
+ camera.setCaptureParams(params);
+ previewMode = on;
+ }
+ }
+
+ /**
+ * This method determines how to take the highest quality image (i.e. the one which has the best
+ * chance of being decoded) given the capabilities of the camera. It is a balancing act between
+ * having enough resolution to read UPCs and having few enough pixels to keep the QR Code
+ * processing fast. The result is the dimensions of the rectangle to capture from the center of
+ * the sensor, plus a stillMultiplier which indicates whether we'll ask the driver to downsample
+ * for us. This has the added benefit of keeping the memory footprint of the bitmap as small as
+ * possible.
+ */
+ private void calculateStillResolution() {
+ cameraResolution = getMaximumCameraResolution();
+ int minDimension = (cameraResolution.x < cameraResolution.y) ? cameraResolution.x :
+ cameraResolution.y;
+ int diagonalResolution = (int) Math.sqrt(cameraResolution.x * cameraResolution.x +
+ cameraResolution.y * cameraResolution.y);
+ float diagonalFov = getFieldOfView();
+
+ // Determine the field of view in the smaller dimension, then calculate how large an object
+ // would be at the minimum focus distance.
+ float fov = diagonalFov * minDimension / diagonalResolution;
+ double objectSize = Math.tan(Math.toRadians(fov / 2.0)) * getMinimumFocusDistance() * 2;
+
+ // Let's assume the largest barcode we might photograph at this distance is 3 inches across. By
+ // cropping to this size, we can avoid processing surrounding pixels, which helps with speed and
+ // accuracy.
+ // TODO(dswitkin): Handle a device with a great macro mode where objectSize < 4 inches.
+ double crop = 3.0 / objectSize;
+ int nativeResolution = (int) (minDimension * crop);
+
+ // The camera driver can only capture images which are a multiple of eight, so it's necessary to
+ // round up.
+ nativeResolution = ((nativeResolution + 7) >> 3) << 3;
+ if (nativeResolution > minDimension) {
+ nativeResolution = minDimension;
+ }
+
+ // There's no point in capturing too much detail, so ask the driver to downsample. I haven't
+ // tried a non-integer multiple, but it seems unlikely to work.
+ double dpi = nativeResolution / objectSize;
+ stillMultiplier = 1;
+ if (dpi > 200) {
+ stillMultiplier = (int) (dpi / 200 + 1);
+ }
+ stillResolution = new Point(nativeResolution, nativeResolution);
+ Log.v(TAG, "FOV " + fov + " objectSize " + objectSize + " crop " + crop + " dpi " + dpi +
+ " nativeResolution " + nativeResolution + " stillMultiplier " + stillMultiplier);
+ }
+
+ // FIXME(dswitkin): These three methods have temporary constants until the new Camera API can
+ // provide the real values for the current device.
+ // Temporary: the camera's maximum resolution in pixels.
+ private static Point getMaximumCameraResolution() {
+ return new Point(1280, 1024);
+ }
+
+ // Temporary: the diagonal field of view in degrees.
+ private static float getFieldOfView() {
+ return 60.0f;
+ }
+
+ // Temporary: the minimum focus distance in inches.
+ private static float getMinimumFocusDistance() {
+ return 12.0f;
+ }
+
+ private Point getScreenResolution() {
+ if (screenResolution == null) {
+ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = manager.getDefaultDisplay();
+ screenResolution = new Point(display.getWidth(), display.getHeight());
+ }
+ return screenResolution;
+ }
+
+}
diff --git a/android-m3/src/com/google/zxing/client/android/CameraSurfaceView.java b/android-m3/src/com/google/zxing/client/android/CameraSurfaceView.java
new file mode 100644
index 000000000..0ab8d2b71
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/CameraSurfaceView.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import com.google.zxing.ResultPoint;
+
+/**
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+
+ private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
+
+ private final CameraManager cameraManager;
+ private final SurfaceHolder surfaceHolder;
+ private boolean hasSurface;
+ private int scannerAlpha;
+
+ CameraSurfaceView(Context context, CameraManager cameraManager) {
+ super(context);
+ this.cameraManager = cameraManager;
+
+ // Install a SurfaceHolder.Callback so we get notified when the underlying surface is created
+ // and destroyed.
+ surfaceHolder = getHolder();
+ surfaceHolder.setCallback(this);
+ hasSurface = false;
+ scannerAlpha = 0;
+ surfaceHolder.setSizeFromLayout();
+ }
+
+ public boolean surfaceCreated(SurfaceHolder holder) {
+ hasSurface = true;
+
+ // Tell the system that we filled the surface in this call. This is a lie to prevent the system
+ // from filling the surface for us automatically. THIS IS REQUIRED because otherwise we'll
+ // access the Surface object from 2 different threads which is not allowed.
+ return true;
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // FIXME(dswitkin): The docs say this surface will be destroyed when this method returns. In
+ // practice this has not been a problem so far. I need to investigate.
+ hasSurface = false;
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ // Surface size or format has changed. This won't happen because of the setFixedSize() call.
+ }
+
+ /**
+ * This method is only called from the WorkerThread. It's job is to grab the next preview frame
+ * from the camera, draw the framing rectangle, and blit everything to the screen.
+ */
+ public void capturePreviewAndDraw() {
+ if (hasSurface) {
+ Canvas canvas = surfaceHolder.lockCanvas();
+ cameraManager.capturePreview(canvas);
+ Rect frame = cameraManager.getFramingRect();
+ int width = canvas.getBitmapWidth();
+ int height = canvas.getBitmapHeight();
+
+ // Draw the exterior (i.e. outside the framing rect) as half darkened
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ paint.setAlpha(96);
+ Rect box = new Rect(0, 0, width, frame.top);
+ canvas.drawRect(box, paint);
+ box.set(0, frame.top, frame.left, frame.bottom + 1);
+ canvas.drawRect(box, paint);
+ box.set(frame.right + 1, frame.top, width, frame.bottom + 1);
+ canvas.drawRect(box, paint);
+ box.set(0, frame.bottom + 1, width, height);
+ canvas.drawRect(box, paint);
+
+ // Draw a two pixel solid black border inside the framing rect
+ paint.setAlpha(255);
+ box.set(frame.left, frame.top, frame.right + 1, frame.top + 2);
+ canvas.drawRect(box, paint);
+ box.set(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1);
+ canvas.drawRect(box, paint);
+ box.set(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1);
+ canvas.drawRect(box, paint);
+ box.set(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1);
+ canvas.drawRect(box, paint);
+
+ // Draw a red "laser scanner" line through the middle
+ paint.setColor(Color.RED);
+ paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
+ int middle = frame.height() / 2 + frame.top;
+ box.set(frame.left + 2, middle - 1, frame.right - 1, middle + 2);
+ canvas.drawRect(box, paint);
+
+ surfaceHolder.unlockCanvasAndPost(canvas);
+
+ // This cheap animation is tied to the rate at which we pull previews from the camera.
+ scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
+ }
+ }
+
+ /**
+ * Draw a line for 1D barcodes (which return two points) or otherwise a set of points returned
+ * from the decoder to indicate what we found.
+ * TODO(dswitkin): It might be nice to clear the framing rect and zoom in on the actual still that
+ * was captured, then paint the green points on it. This would also clear the red scanner line
+ * which doesn't make sense after the capture.
+ *
+ * @param resultPoints An array of points from the decoder, whose coordinates are expressed
+ * relative to the still image from the camera.
+ */
+ public void drawResultPoints(ResultPoint[] resultPoints) {
+ if (hasSurface) {
+ Canvas canvas = surfaceHolder.lockCanvas();
+ Paint paint = new Paint();
+ paint.setColor(Color.GREEN);
+ paint.setAlpha(128);
+
+ Point[] points = cameraManager.convertResultPoints(resultPoints);
+ if (points.length == 2) {
+ paint.setStrokeWidth(4);
+ canvas.drawLine(points[0].x, points[0].y, points[1].x, points[1].y, paint);
+ } else {
+ paint.setStrokeWidth(10);
+ for (int x = 0; x < points.length; x++) {
+ canvas.drawPoint(points[x].x, points[x].y, paint);
+ }
+ }
+
+ surfaceHolder.unlockCanvasAndPost(canvas);
+ }
+ }
+
+}
diff --git a/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java b/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java
new file mode 100755
index 000000000..6d3f2bd8f
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.graphics.Bitmap;
+import com.google.zxing.BlackPointEstimationMethod;
+import com.google.zxing.MonochromeBitmapSource;
+import com.google.zxing.ReaderException;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BlackPointEstimator;
+
+/**
+ * This object implements MonochromeBitmapSource around an Android Bitmap. Rather than capturing an
+ * RGB image and calculating the grey value at each pixel, we ask the camera driver for YUV data and
+ * strip out the luminance channel directly. This should be faster but provides fewer bits, i.e.
+ * fewer grey levels.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author srowen@google.com (Sean Owen)
+ */
+final class RGBMonochromeBitmapSource implements MonochromeBitmapSource {
+
+ private final Bitmap image;
+ private int blackPoint;
+ private BlackPointEstimationMethod lastMethod;
+ private int lastArgument;
+
+ private static final int LUMINANCE_BITS = 5;
+ private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
+ private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
+
+ RGBMonochromeBitmapSource(Bitmap image) {
+ this.image = image;
+ blackPoint = 0x7F;
+ lastMethod = null;
+ lastArgument = 0;
+ }
+
+ public boolean isBlack(int x, int y) {
+ return computeRGBLuminance(image.getPixel(x, y)) < blackPoint;
+ }
+
+ public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) {
+ if (row == null) {
+ row = new BitArray(getWidth);
+ } else {
+ row.clear();
+ }
+ int[] pixelRow = new int[getWidth];
+ image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1);
+ for (int i = 0; i < getWidth; i++) {
+ if (computeRGBLuminance(pixelRow[i]) < blackPoint) {
+ row.set(i);
+ }
+ }
+ return row;
+ }
+
+ public int getHeight() {
+ return image.height();
+ }
+
+ public int getWidth() {
+ return image.width();
+ }
+
+ public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException {
+ if (!method.equals(lastMethod) || argument != lastArgument) {
+ int width = image.width();
+ int height = image.height();
+ int[] histogram = new int[LUMINANCE_BUCKETS];
+ if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
+ int minDimension = width < height ? width : height;
+ int startI = height == minDimension ? 0 : (height - width) >> 1;
+ int startJ = width == minDimension ? 0 : (width - height) >> 1;
+ for (int n = 0; n < minDimension; n++) {
+ int pixel = image.getPixel(startJ + n, startI + n);
+ histogram[computeRGBLuminance(pixel) >> LUMINANCE_SHIFT]++;
+ }
+ } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
+ if (argument < 0 || argument >= height) {
+ throw new IllegalArgumentException("Row is not within the image: " + argument);
+ }
+ int[] pixelRow = new int[width];
+ image.getPixels(pixelRow, 0, width, 0, argument, width, 1);
+ for (int x = 0; x < width; x++) {
+ histogram[computeRGBLuminance(pixelRow[x]) >> LUMINANCE_SHIFT]++;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown method: " + method);
+ }
+ blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
+ lastMethod = method;
+ lastArgument = argument;
+ }
+ }
+
+ public BlackPointEstimationMethod getLastEstimationMethod() {
+ return lastMethod;
+ }
+
+ public MonochromeBitmapSource rotateCounterClockwise() {
+ throw new IllegalStateException("Rotate not supported");
+ }
+
+ public boolean isRotateSupported() {
+ return false;
+ }
+
+ /**
+ * An optimized approximation of a more proper conversion from RGB to luminance which
+ * only uses shifts. See BufferedImageMonochromeBitmapSource for an original version.
+ */
+ private static int computeRGBLuminance(int pixel) {
+ // Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that
+ // the multiplies can be implemented as shifts.
+ //
+ // Really, it's:
+ //
+ // return ((((pixel >> 16) & 0xFF) << 8) +
+ // (((pixel >> 8) & 0xFF) << 9) +
+ // (( pixel & 0xFF) << 8)) >> 10;
+ //
+ // That is, we're replacing the coefficients in the original with powers of two,
+ // which can be implemented as shifts, even though changing the coefficients slightly
+ // corrupts the conversion. Not significant for our purposes.
+ //
+ // But we can get even cleverer and eliminate a few shifts:
+ return (((pixel & 0x00FF0000) >> 8) +
+ ((pixel & 0x0000FF00) << 1) +
+ ((pixel & 0x000000FF) << 8)) >> 10;
+ }
+
+}
\ No newline at end of file
diff --git a/android-m3/src/com/google/zxing/client/android/ResultHandler.java b/android-m3/src/com/google/zxing/client/android/ResultHandler.java
new file mode 100755
index 000000000..cfe4df607
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/ResultHandler.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.content.Intent;
+import android.net.ContentURI;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Contacts;
+import com.google.zxing.client.result.AddressBookAUParsedResult;
+import com.google.zxing.client.result.AddressBookDoCoMoParsedResult;
+import com.google.zxing.client.result.BookmarkDoCoMoParsedResult;
+import com.google.zxing.client.result.EmailAddressParsedResult;
+import com.google.zxing.client.result.EmailDoCoMoParsedResult;
+import com.google.zxing.client.result.GeoParsedResult;
+import com.google.zxing.client.result.ParsedReaderResult;
+import com.google.zxing.client.result.ParsedReaderResultType;
+import com.google.zxing.client.result.TelParsedResult;
+import com.google.zxing.client.result.UPCParsedResult;
+import com.google.zxing.client.result.URIParsedResult;
+import com.google.zxing.client.result.URLTOParsedResult;
+
+import java.net.URISyntaxException;
+
+/**
+ * Handles the result of barcode decoding in the context of the Android platform,
+ * by dispatching the proper intents and so on.
+ *
+ * @author srowen@google.com (Sean Owen)
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class ResultHandler extends Handler {
+
+ private final Intent intent;
+ private final BarcodeReaderCaptureActivity captureActivity;
+
+ ResultHandler(BarcodeReaderCaptureActivity captureActivity, ParsedReaderResult result) {
+ this.captureActivity = captureActivity;
+ this.intent = resultToIntent(result);
+ }
+
+ private static Intent resultToIntent(ParsedReaderResult result) {
+ Intent intent = null;
+ ParsedReaderResultType type = result.getType();
+ if (type.equals(ParsedReaderResultType.ADDRESSBOOK)) {
+ AddressBookDoCoMoParsedResult addressResult = (AddressBookDoCoMoParsedResult) result;
+ intent = new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
+ putExtra(intent, Contacts.Intents.Insert.NAME, addressResult.getName());
+ putExtra(intent, Contacts.Intents.Insert.PHONE, addressResult.getPhoneNumbers());
+ putExtra(intent, Contacts.Intents.Insert.EMAIL, addressResult.getEmail());
+ putExtra(intent, Contacts.Intents.Insert.NOTES, addressResult.getNote());
+ putExtra(intent, Contacts.Intents.Insert.POSTAL, addressResult.getAddress());
+ } else if (type.equals(ParsedReaderResultType.ADDRESSBOOK_AU)) {
+ AddressBookAUParsedResult addressResult = (AddressBookAUParsedResult) result;
+ intent = new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
+ putExtra(intent, Contacts.Intents.Insert.NAME, addressResult.getNames());
+ putExtra(intent, Contacts.Intents.Insert.PHONE, addressResult.getPhoneNumbers());
+ putExtra(intent, Contacts.Intents.Insert.EMAIL, addressResult.getEmails());
+ putExtra(intent, Contacts.Intents.Insert.NOTES, addressResult.getNote());
+ putExtra(intent, Contacts.Intents.Insert.POSTAL, addressResult.getAddress());
+ } else if (type.equals(ParsedReaderResultType.BOOKMARK)) {
+ // For now, we can only open the browser, and not actually add a bookmark
+ try {
+ intent = new Intent(Intent.VIEW_ACTION, new ContentURI(((BookmarkDoCoMoParsedResult) result).getURI()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.URLTO)) {
+ try {
+ intent = new Intent(Intent.VIEW_ACTION, new ContentURI(((URLTOParsedResult) result).getURI()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.EMAIL)) {
+ EmailDoCoMoParsedResult emailResult = (EmailDoCoMoParsedResult) result;
+ try {
+ intent = new Intent(Intent.SENDTO_ACTION, new ContentURI(emailResult.getTo()));
+ } catch (URISyntaxException e) {
+ }
+ putExtra(intent, "subject", emailResult.getSubject());
+ putExtra(intent, "body", emailResult.getBody());
+ } else if (type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) {
+ EmailAddressParsedResult emailResult = (EmailAddressParsedResult) result;
+ try {
+ intent = new Intent(Intent.SENDTO_ACTION, new ContentURI(emailResult.getEmailAddress()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.TEL)) {
+ TelParsedResult telResult = (TelParsedResult) result;
+ try {
+ intent = new Intent(Intent.DIAL_ACTION, new ContentURI("tel:" + telResult.getNumber()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.GEO)) {
+ GeoParsedResult geoResult = (GeoParsedResult) result;
+ try {
+ intent = new Intent(Intent.VIEW_ACTION, new ContentURI(geoResult.getGeoURI()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.UPC)) {
+ UPCParsedResult upcResult = (UPCParsedResult) result;
+ try {
+ ContentURI uri = new ContentURI("http://www.upcdatabase.com/item.asp?upc=" + upcResult.getUPC());
+ intent = new Intent(Intent.VIEW_ACTION, uri);
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.URI)) {
+ URIParsedResult uriResult = (URIParsedResult) result;
+ try {
+ intent = new Intent(Intent.VIEW_ACTION, new ContentURI(uriResult.getURI()));
+ } catch (URISyntaxException e) {
+ }
+ } else if (type.equals(ParsedReaderResultType.ANDROID_INTENT)) {
+ intent = ((AndroidIntentParsedResult) result).getIntent();
+ }
+ return intent;
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == R.string.button_yes) {
+ if (intent != null) {
+ captureActivity.startActivity(intent);
+ }
+ } else {
+ captureActivity.restartPreview();
+ }
+ }
+
+ Intent getIntent() {
+ return intent;
+ }
+
+ private static void putExtra(Intent intent, String key, String value) {
+ if (value != null && value.length() > 0) {
+ intent.putExtra(key, value);
+ }
+ }
+
+ private static void putExtra(Intent intent, String key, String[] value) {
+ if (value != null && value.length > 0) {
+ putExtra(intent, key, value[0]);
+ }
+ }
+
+}
diff --git a/android-m3/src/com/google/zxing/client/android/WorkerThread.java b/android-m3/src/com/google/zxing/client/android/WorkerThread.java
new file mode 100644
index 000000000..c0f6b3541
--- /dev/null
+++ b/android-m3/src/com/google/zxing/client/android/WorkerThread.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.google.zxing.client.android;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Message;
+import com.google.zxing.MonochromeBitmapSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+
+/**
+ * This thread does all the heavy lifting, both during preview and for the final capture and
+ * decoding. That leaves the main thread free to handle UI tasks.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class WorkerThread extends Thread {
+
+ private final CameraSurfaceView surfaceView;
+ private final CameraManager cameraManager;
+ private final Handler handler;
+ private final Object idleLock;
+ private State state;
+
+ private enum State {
+ IDLE,
+ PREVIEW_LOOP,
+ STILL_AND_DECODE,
+ DONE
+ }
+
+ WorkerThread(CameraSurfaceView surfaceView, CameraManager cameraManager, Handler handler) {
+ this.surfaceView = surfaceView;
+ this.cameraManager = cameraManager;
+ this.handler = handler;
+ this.idleLock = new Object();
+ state = State.IDLE;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ switch (state) {
+ case IDLE:
+ idle();
+ break;
+ case PREVIEW_LOOP:
+ surfaceView.capturePreviewAndDraw();
+ break;
+ case STILL_AND_DECODE:
+ Bitmap bitmap = cameraManager.captureStill();
+ Result rawResult;
+ try {
+ MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap);
+ rawResult = new MultiFormatReader().decode(source);
+ } catch (ReaderException e) {
+ Message message = Message.obtain(handler, R.id.decoding_failed_message);
+ message.sendToTarget();
+ state = State.PREVIEW_LOOP;
+ break;
+ }
+ Message message = Message.obtain(handler, R.id.decoding_succeeded_message, rawResult);
+ message.sendToTarget();
+ state = State.IDLE;
+ break;
+ case DONE:
+ return;
+ }
+ }
+ }
+
+ public void requestPreviewLoop() {
+ state = State.PREVIEW_LOOP;
+ wakeFromIdle();
+ }
+
+ public void requestStillAndDecode() {
+ state = State.STILL_AND_DECODE;
+ wakeFromIdle();
+ }
+
+ public void requestExitAndWait() {
+ state = State.DONE;
+ wakeFromIdle();
+ try {
+ join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private void idle() {
+ try {
+ synchronized (idleLock) {
+ idleLock.wait();
+ }
+ } catch (InterruptedException ie) {
+ // continue
+ }
+ }
+
+ private void wakeFromIdle() {
+ synchronized (idleLock) {
+ idleLock.notifyAll();
+ }
+ }
+
+}
diff --git a/android-m3/strings.xml.template b/android-m3/strings.xml.template
new file mode 100644
index 000000000..e9af99e21
--- /dev/null
+++ b/android-m3/strings.xml.template
@@ -0,0 +1,35 @@
+
+
+
+ Barcode Reader
+ No
+ OK
+ Yes
+ About...
+ ZXing Barcode Reader v@VERSION@\nhttp://code.google.com/p/zxing
+ Sorry, no barcode was found.
+ About
+ Barcode Detected
+ No Barcode Detected
+ Error
+ Open Web Page?
+ Add Contact?
+ Compose E-mail?
+ Look Up Barcode Online?
+ Dial Number?
+ View In Google Maps?
+