From e9ed806d30b6e4dd7a9ab49132632c4cde66eed3 Mon Sep 17 00:00:00 2001 From: dswitkin Date: Tue, 8 Apr 2008 15:25:13 +0000 Subject: [PATCH] Rewrote the Android M3 client to do continuous decoding, which means you no longer have to push the shutter button. Now you can just place the barcode in the viewfinder and it will display the contents as soon as it decodes them. That also means you no longer get "barcode not found" error dialogs which is a big improvement. Also made sure that capturing debug JPEGs uses unique filenames. git-svn-id: https://zxing.googlecode.com/svn/trunk@352 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- android-m3/res/values/ids.xml | 15 +- .../android/BarcodeReaderCaptureActivity.java | 72 +++--- .../zxing/client/android/CameraManager.java | 39 ++- .../zxing/client/android/CameraThread.java | 144 +++++++++++ .../zxing/client/android/DecodeThread.java | 200 +++++++++++++++ .../android/RGBMonochromeBitmapSource.java | 10 +- .../zxing/client/android/WorkerThread.java | 238 ------------------ android-m3/strings.xml.template | 15 +- 8 files changed, 439 insertions(+), 294 deletions(-) create mode 100644 android-m3/src/com/google/zxing/client/android/CameraThread.java create mode 100644 android-m3/src/com/google/zxing/client/android/DecodeThread.java delete mode 100644 android-m3/src/com/google/zxing/client/android/WorkerThread.java diff --git a/android-m3/res/values/ids.xml b/android-m3/res/values/ids.xml index 776e72925..be39f8eaa 100644 --- a/android-m3/res/values/ids.xml +++ b/android-m3/res/values/ids.xml @@ -15,6 +15,17 @@ limitations under the License. --> - - + + + + + + + + + + + + + diff --git a/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java b/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java index d950630e9..78e578f20 100644 --- a/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java +++ b/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java @@ -32,8 +32,6 @@ import com.google.zxing.ResultPoint; import com.google.zxing.client.result.ParsedReaderResult; import com.google.zxing.client.result.ParsedReaderResultType; -import java.util.Date; - /** * The barcode reader activity itself. This is loosely based on the CameraPreview * example included in the Android SDK. @@ -45,10 +43,10 @@ public final class BarcodeReaderCaptureActivity extends Activity { private CameraManager cameraManager; private CameraSurfaceView surfaceView; - private WorkerThread workerThread; - private Date decodeStart; + private CameraThread cameraThread; private static final int ABOUT_ID = Menu.FIRST; + private static final int HELP_ID = Menu.FIRST + 1; @Override public void onCreate(Bundle icicle) { @@ -64,9 +62,8 @@ public final class BarcodeReaderCaptureActivity extends Activity { cameraManager = new CameraManager(getApplication()); surfaceView = new CameraSurfaceView(getApplication(), cameraManager); setContentView(surfaceView); - workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler); - workerThread.requestPreviewLoop(); - workerThread.start(); + cameraThread = new CameraThread(this, surfaceView, cameraManager, messageHandler); + cameraThread.start(); // TODO re-enable this when issues with Matrix.setPolyToPoly() are resolved //GridSampler.setGridSampler(new AndroidGraphicsGridSampler()); @@ -84,36 +81,38 @@ public final class BarcodeReaderCaptureActivity extends Activity { protected void onResume() { super.onResume(); cameraManager.openDriver(); - if (workerThread == null) { - workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler); - workerThread.requestPreviewLoop(); - workerThread.start(); + if (cameraThread == null) { + cameraThread = new CameraThread(this, surfaceView, cameraManager, messageHandler); + cameraThread.start(); } } @Override protected void onPause() { super.onPause(); - if (workerThread != null) { - workerThread.requestExitAndWait(); - workerThread = null; + if (cameraThread != null) { + Message quit = Message.obtain(cameraThread.handler, R.id.quit); + quit.sendToTarget(); + cameraThread = null; } cameraManager.closeDriver(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { - decodeStart = new Date(); - workerThread.requestStillAndDecode(); - } else if (keyCode == KeyEvent.KEYCODE_Q) { - decodeStart = new Date(); - workerThread.requestStillAndDecodeQR(); - } else if (keyCode == KeyEvent.KEYCODE_U) { - decodeStart = new Date(); - workerThread.requestStillAndDecode1D(); + if (keyCode == KeyEvent.KEYCODE_A) { + cameraThread.setDecodeAllMode(); } else if (keyCode == KeyEvent.KEYCODE_C) { - workerThread.requestStillAndSave(); + Message save = Message.obtain(cameraThread.handler, R.id.save); + save.sendToTarget(); + } else if (keyCode == KeyEvent.KEYCODE_P) { + cameraManager.setUsePreviewForDecode(true); + } else if (keyCode == KeyEvent.KEYCODE_Q) { + cameraThread.setDecodeQRMode(); + } else if (keyCode == KeyEvent.KEYCODE_S) { + cameraManager.setUsePreviewForDecode(false); + } else if (keyCode == KeyEvent.KEYCODE_U) { + cameraThread.setDecode1DMode(); } else { return super.onKeyDown(keyCode, event); } @@ -124,18 +123,24 @@ public final class BarcodeReaderCaptureActivity extends Activity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, ABOUT_ID, R.string.menu_about); + menu.add(0, HELP_ID, R.string.menu_help); return true; } @Override public boolean onOptionsItemSelected(Menu.Item item) { + Context context = getApplication(); 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; + case HELP_ID: + showAlert(context.getString(R.string.title_help), + context.getString(R.string.msg_help), + context.getString(R.string.button_ok), null, true, null); + break; } return super.onOptionsItemSelected(item); } @@ -143,29 +148,22 @@ public final class BarcodeReaderCaptureActivity extends Activity { private final Handler messageHandler = new Handler() { @Override public void handleMessage(Message message) { - Date now = new Date(); - long duration = now.getTime() - decodeStart.getTime(); switch (message.what) { - case R.id.decoding_succeeded_message: + case R.id.decode_succeeded: + int duration = message.arg1; handleDecode((Result) message.obj, duration); break; - case R.id.decoding_failed_message: - Context context = getApplication(); - String title = context.getString(R.string.title_no_barcode_detected) + - " (" + duration + " ms)"; - showAlert(title, context.getString(R.string.msg_no_barcode_detected), - context.getString(R.string.button_ok), null, true, null); - break; } } }; public void restartPreview() { - workerThread.requestPreviewLoop(); + Message restart = Message.obtain(cameraThread.handler, R.id.restart_preview); + restart.sendToTarget(); } // TODO(dswitkin): These deprecated showAlert calls need to be updated. - private void handleDecode(Result rawResult, long duration) { + private void handleDecode(Result rawResult, int duration) { ResultPoint[] points = rawResult.getResultPoints(); if (points != null && points.length > 0) { surfaceView.drawResultPoints(points); diff --git a/android-m3/src/com/google/zxing/client/android/CameraManager.java b/android-m3/src/com/google/zxing/client/android/CameraManager.java index fb40bd291..51cbb869d 100644 --- a/android-m3/src/com/google/zxing/client/android/CameraManager.java +++ b/android-m3/src/com/google/zxing/client/android/CameraManager.java @@ -45,17 +45,21 @@ final class CameraManager { private int stillMultiplier; private Point screenResolution; private Rect framingRect; - private final Bitmap bitmap; + private Bitmap bitmap; private CameraDevice camera; private final CameraDevice.CaptureParams params; private boolean previewMode; + private boolean usePreviewForDecode; CameraManager(Context context) { this.context = context; getScreenResolution(); calculateStillResolution(); calculatePreviewResolution(); - bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false); + + usePreviewForDecode = true; + setUsePreviewForDecode(false); + camera = CameraDevice.open(); params = new CameraDevice.CaptureParams(); previewMode = false; @@ -84,12 +88,31 @@ final class CameraManager { } public Bitmap captureStill() { - setPreviewMode(false); + setPreviewMode(usePreviewForDecode); Canvas canvas = new Canvas(bitmap); camera.capture(canvas); return bitmap; } + /** + * This method exists to help us evaluate how to best set up and use the camera. + * @param usePreview Decode at preview resolution if true, else use still resolution. + */ + public void setUsePreviewForDecode(boolean usePreview) { + if (usePreviewForDecode != usePreview) { + usePreviewForDecode = usePreview; + if (usePreview) { + Log.v(TAG, "Creating bitmap at screen resolution: " + screenResolution.x + "," + + screenResolution.y); + bitmap = Bitmap.createBitmap(screenResolution.x, screenResolution.y, false); + } else { + Log.v(TAG, "Creating bitmap at still resolution: " + screenResolution.x + "," + + screenResolution.y); + bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false); + } + } + } + /** * 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 @@ -104,6 +127,7 @@ final class CameraManager { int leftOffset = (screenResolution.x - size) / 2; int topOffset = (screenResolution.y - size) / 2; framingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size); + Log.v(TAG, "Calculated framing rect: " + framingRect); } return framingRect; } @@ -122,8 +146,13 @@ final class CameraManager { 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); + if (usePreviewForDecode) { + output[x].x = (int) (points[x].getX() + 0.5f); + output[x].y = (int) (points[x].getY() + 0.5f); + } else { + 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; } diff --git a/android-m3/src/com/google/zxing/client/android/CameraThread.java b/android-m3/src/com/google/zxing/client/android/CameraThread.java new file mode 100644 index 000000000..fa2a2acf4 --- /dev/null +++ b/android-m3/src/com/google/zxing/client/android/CameraThread.java @@ -0,0 +1,144 @@ +/* + * 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.os.Handler; +import android.os.Looper; +import android.os.Message; + +/** + * This thread continuously pulls preview frames from the camera and draws them to the screen. It + * also asks the DecodeThread to process as many images as it can keep up with, and coordinates with + * the main thread to display the results. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class CameraThread extends Thread { + + public Handler handler; + + private final CameraSurfaceView surfaceView; + private final Handler activityHandler; + private final DecodeThread decodeThread; + private boolean requestDecode; + private boolean suspendPreview; + + CameraThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView, + CameraManager cameraManager, Handler activityHandler) { + this.surfaceView = surfaceView; + this.activityHandler = activityHandler; + + decodeThread = new DecodeThread(activity, cameraManager); + decodeThread.start(); + requestDecode = true; + suspendPreview = false; + } + + @Override + public void run() { + Looper.prepare(); + handler = new Handler() { + public void handleMessage(Message message) { + switch (message.what) { + case R.id.preview: + if (!suspendPreview) { + surfaceView.capturePreviewAndDraw(); + } + break; + case R.id.save: + suspendPreview = true; + Message save = Message.obtain(decodeThread.handler, R.id.save); + save.sendToTarget(); + break; + case R.id.restart_preview: + restartPreviewAndDecode(); + return; + case R.id.quit: + Message quit = Message.obtain(decodeThread.handler, R.id.quit); + quit.sendToTarget(); + Looper.myLooper().quit(); + break; + case R.id.decode_started: + // Since the decoder is done with the camera, continue fetching preview frames. + suspendPreview = false; + break; + case R.id.decode_succeeded: + // Message.copyFrom() did not work as expected, hence this workaround. + Message success = Message.obtain(activityHandler, R.id.decode_succeeded, message.obj); + success.arg1 = message.arg1; + success.sendToTarget(); + suspendPreview = true; + break; + case R.id.decode_failed: + // We're decoding as fast as possible, so when one fails, start another. + requestDecode = true; + break; + case R.id.save_succeeded: + // TODO: Put up a non-blocking status message + restartPreviewAndDecode(); + break; + case R.id.save_failed: + // TODO: Put up a blocking error message + restartPreviewAndDecode(); + return; + } + + if (requestDecode) { + requestDecode = false; + suspendPreview = true; + Message decode = Message.obtain(decodeThread.handler, R.id.decode); + decode.sendToTarget(); + } else if (!suspendPreview) { + Message preview = Message.obtain(handler, R.id.preview); + preview.sendToTarget(); + } + } + }; + decodeThread.setCameraThreadHandler(handler); + + // Start ourselves capturing previews + Message preview = Message.obtain(handler, R.id.preview); + preview.sendToTarget(); + Looper.loop(); + } + + public void setDecodeAllMode() { + Message message = Message.obtain(decodeThread.handler, R.id.set_decode_all_mode); + message.sendToTarget(); + } + + public void setDecode1DMode() { + Message message = Message.obtain(decodeThread.handler, R.id.set_decode_1D_mode); + message.sendToTarget(); + } + + public void setDecodeQRMode() { + Message message = Message.obtain(decodeThread.handler, R.id.set_decode_QR_mode); + message.sendToTarget(); + } + + /** + * Take one preview to update the screen, then do a decode and continue previews. + */ + private void restartPreviewAndDecode() { + requestDecode = true; + suspendPreview = false; + Message preview = Message.obtain(handler, R.id.preview); + preview.sendToTarget(); + } + +} diff --git a/android-m3/src/com/google/zxing/client/android/DecodeThread.java b/android-m3/src/com/google/zxing/client/android/DecodeThread.java new file mode 100644 index 000000000..efdcfef28 --- /dev/null +++ b/android-m3/src/com/google/zxing/client/android/DecodeThread.java @@ -0,0 +1,200 @@ +/* + * 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.Application; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.MonochromeBitmapSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; + +/** + * This thread does all the heavy lifting of decoding the images. It can also save images to flash + * for debugging purposes. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class DecodeThread extends Thread { + + public Handler handler; + + private final BarcodeReaderCaptureActivity activity; + private final CameraManager cameraManager; + private Hashtable hints; + private Handler cameraThreadHandler; + + DecodeThread(BarcodeReaderCaptureActivity activity, CameraManager cameraManager) { + this.activity = activity; + this.cameraManager = cameraManager; + } + + @Override + public void run() { + Looper.prepare(); + handler = new Handler() { + public void handleMessage(Message message) { + switch (message.what) { + case R.id.decode: + captureAndDecode(); + break; + case R.id.save: + captureAndSave(); + break; + case R.id.quit: + Looper.myLooper().quit(); + break; + case R.id.set_decode_all_mode: + setDecodeAllMode(); + break; + case R.id.set_decode_1D_mode: + setDecode1DMode(); + break; + case R.id.set_decode_QR_mode: + setDecodeQRMode(); + break; + } + } + }; + Looper.loop(); + } + + public void setCameraThreadHandler(Handler cameraThreadHandler) { + this.cameraThreadHandler = cameraThreadHandler; + } + + private void setDecodeAllMode() { + hints = null; + } + + // TODO: This is fragile in case we add new formats. It would be better to have a new enum + // value which represented all 1D formats. + private void setDecode1DMode() { + hints = new Hashtable(3); + Vector vector = new Vector(); + vector.addElement(BarcodeFormat.UPC_A); + vector.addElement(BarcodeFormat.UPC_E); + vector.addElement(BarcodeFormat.EAN_13); + vector.addElement(BarcodeFormat.EAN_8); + vector.addElement(BarcodeFormat.CODE_39); + vector.addElement(BarcodeFormat.CODE_128); + hints.put(DecodeHintType.POSSIBLE_FORMATS, vector); + } + + private void setDecodeQRMode() { + hints = new Hashtable(3); + Vector vector = new Vector(); + vector.addElement(BarcodeFormat.QR_CODE); + hints.put(DecodeHintType.POSSIBLE_FORMATS, vector); + } + + private void captureAndDecode() { + Date startDate = new Date(); + Bitmap bitmap = cameraManager.captureStill(); + // Let the CameraThread know it can resume previews while the decoding continues in parallel. + Message restart = Message.obtain(cameraThreadHandler, R.id.decode_started); + restart.sendToTarget(); + + Result rawResult; + try { + MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap); + rawResult = new MultiFormatReader().decode(source, hints); + } catch (ReaderException e) { + Message failure = Message.obtain(cameraThreadHandler, R.id.decode_failed); + failure.sendToTarget(); + return; + } + Date endDate = new Date(); + Message success = Message.obtain(cameraThreadHandler, R.id.decode_succeeded, rawResult); + success.arg1 = (int) (endDate.getTime() - startDate.getTime()); + success.sendToTarget(); + } + + /** + * This is a debugging feature used to take photos and save them as JPEGs using the exact camera + * setup as in normal decoding. This is useful for building up a library of test images. + */ + private void captureAndSave() { + Bitmap bitmap = cameraManager.captureStill(); + OutputStream outStream = getNewPhotoOutputStream(); + if (outStream != null) { + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream); + try { + outStream.close(); + } catch (IOException e) { + } + Message success = Message.obtain(cameraThreadHandler, R.id.save_succeeded); + success.sendToTarget(); + } else { + Message failure = Message.obtain(cameraThreadHandler, R.id.save_failed); + failure.sendToTarget(); + } + } + + /** + * We prefer to write to the SD Card because it has more space, and is automatically mounted as a + * drive over USB. If it's not present, fall back to the package's private file area here: + * + * /data/data/com.google.zxing.client.android/files + * + * @return A stream which represents the new file where the photo will be saved. + */ + private OutputStream getNewPhotoOutputStream() { + File sdcard = new File("/sdcard"); + if (sdcard.exists()) { + File barcodes = new File(sdcard, "barcodes"); + if (!barcodes.exists()) { + if (!barcodes.mkdir()) { + return null; + } + } + String fileName = getNewPhotoName(); + try { + return new FileOutputStream(new File(barcodes, fileName)); + } catch (FileNotFoundException e) { + } + } else { + Application application = activity.getApplication(); + String fileName = getNewPhotoName(); + try { + return application.openFileOutput(fileName, 0); + } catch (FileNotFoundException e) { + } + } + return null; + } + + private String getNewPhotoName() { + Date now = new Date(); + return "capture" + now.getTime() + ".jpg"; + } + +} diff --git a/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java b/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java index 100a9d466..5e6faa057 100755 --- a/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java +++ b/android-m3/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java @@ -24,10 +24,7 @@ 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. + * This object implements MonochromeBitmapSource around an Android Bitmap. * * @author dswitkin@google.com (Daniel Switkin) * @author srowen@google.com (Sean Owen) @@ -61,7 +58,7 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource { row.clear(); } int[] pixelRow = new int[getWidth]; - image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1); + image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1); for (int i = 0; i < getWidth; i++) { if (computeRGBLuminance(pixelRow[i]) < blackPoint) { row.set(i); @@ -124,6 +121,9 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource { /** * An optimized approximation of a more proper conversion from RGB to luminance which * only uses shifts. See BufferedImageMonochromeBitmapSource for an original version. + * + * @param pixel An ARGB input pixel + * @return An eight bit luminance value */ private static int computeRGBLuminance(int pixel) { // Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that diff --git a/android-m3/src/com/google/zxing/client/android/WorkerThread.java b/android-m3/src/com/google/zxing/client/android/WorkerThread.java deleted file mode 100644 index 012bd1c4f..000000000 --- a/android-m3/src/com/google/zxing/client/android/WorkerThread.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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.Application; -import android.graphics.Bitmap; -import android.os.Handler; -import android.os.Message; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.MonochromeBitmapSource; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.ReaderException; -import com.google.zxing.Result; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Hashtable; -import java.util.Vector; - -/** - * 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 BarcodeReaderCaptureActivity activity; - 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, - STILL_AND_DECODE_1D, - STILL_AND_DECODE_QR, - STILL_AND_SAVE, - DONE - } - - WorkerThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView, - CameraManager cameraManager, Handler handler) { - this.activity = activity; - 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: - takeStillAndDecode(null); - break; - case STILL_AND_DECODE_1D: { - Hashtable hints = new Hashtable(3); - // TODO: This is fragile in case we add new formats. It would be better to have a new enum - // value which represented all 1D formats. - Vector vector = new Vector(); - vector.addElement(BarcodeFormat.UPC_A); - vector.addElement(BarcodeFormat.UPC_E); - vector.addElement(BarcodeFormat.EAN_13); - vector.addElement(BarcodeFormat.EAN_8); - vector.addElement(BarcodeFormat.CODE_39); - vector.addElement(BarcodeFormat.CODE_128); - hints.put(DecodeHintType.POSSIBLE_FORMATS, vector); - takeStillAndDecode(hints); - break; - } - case STILL_AND_DECODE_QR: { - Hashtable hints = new Hashtable(3); - Vector vector = new Vector(); - vector.addElement(BarcodeFormat.QR_CODE); - hints.put(DecodeHintType.POSSIBLE_FORMATS, vector); - takeStillAndDecode(hints); - break; - } - case STILL_AND_SAVE: - takeStillAndSave(); - break; - case DONE: - return; - } - } - } - - public void requestPreviewLoop() { - state = State.PREVIEW_LOOP; - wakeFromIdle(); - } - - public void requestStillAndDecode() { - state = State.STILL_AND_DECODE; - wakeFromIdle(); - } - - public void requestStillAndDecode1D() { - state = State.STILL_AND_DECODE_1D; - wakeFromIdle(); - } - - public void requestStillAndDecodeQR() { - state = State.STILL_AND_DECODE_QR; - wakeFromIdle(); - } - - public void requestStillAndSave() { - state = State.STILL_AND_SAVE; - wakeFromIdle(); - } - - public void requestExitAndWait() { - state = State.DONE; - wakeFromIdle(); - try { - join(); - } catch (InterruptedException e) { - } - } - - private void idle() { - try { - synchronized (idleLock) { - idleLock.wait(); - } - } catch (InterruptedException e) { - // Continue - } - } - - private void wakeFromIdle() { - synchronized (idleLock) { - idleLock.notifyAll(); - } - } - - private void takeStillAndDecode(Hashtable hints) { - Bitmap bitmap = cameraManager.captureStill(); - Result rawResult; - try { - MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap); - rawResult = new MultiFormatReader().decode(source, hints); - } catch (ReaderException e) { - Message message = Message.obtain(handler, R.id.decoding_failed_message); - message.sendToTarget(); - state = State.PREVIEW_LOOP; - return; - } - Message message = Message.obtain(handler, R.id.decoding_succeeded_message, rawResult); - message.sendToTarget(); - state = State.IDLE; - } - - /** - * This is a debugging feature used to take photos and save them as JPEGs using the exact camera - * setup as in normal decoding. This is useful for building up a library of test images. - */ - private void takeStillAndSave() { - Bitmap bitmap = cameraManager.captureStill(); - OutputStream outStream = getNewPhotoOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream); - try { - outStream.close(); - } catch (IOException e) { - } - state = State.PREVIEW_LOOP; - } - - /** - * We prefer to write to the SD Card because it has more space, and is automatically mounted as a - * drive over USB. If it's not present, fall back to the package's private file area here: - * - * /data/data/com.google.zxing.client.android/files - * - * @return A stream which represents the new file where the photo will be saved. - */ - private OutputStream getNewPhotoOutputStream() { - File sdcard = new File("/sdcard"); - if (sdcard.exists()) { - File barcodes = new File(sdcard, "barcodes"); - if (!barcodes.exists()) { - if (!barcodes.mkdir()) { - return null; - } - } - String fileName = getNewPhotoName(barcodes.list()); - try { - return new FileOutputStream(new File(barcodes, fileName)); - } catch (FileNotFoundException e) { - } - } else { - Application application = activity.getApplication(); - String fileName = getNewPhotoName(application.fileList()); - try { - return application.openFileOutput(fileName, 0); - } catch (FileNotFoundException e) { - } - } - return null; - } - - private String getNewPhotoName(String[] listOfFiles) { - int existingFileCount = (listOfFiles != null) ? listOfFiles.length : 0; - return "capture" + existingFileCount + ".jpg"; - } - -} diff --git a/android-m3/strings.xml.template b/android-m3/strings.xml.template index e9af99e21..3b5d67278 100644 --- a/android-m3/strings.xml.template +++ b/android-m3/strings.xml.template @@ -20,16 +20,17 @@ OK Yes About... + Help... ZXing Barcode Reader v@VERSION@\nhttp://code.google.com/p/zxing - Sorry, no barcode was found. + A: Decode all barcodes\nC: Capture and save a JPEG\nP: Use the preview image for decoding\nQ: Decode only QR Codes\nS: Use a still image for decoding\nU: Decode only UPC/1D barcodes About - Barcode Detected - No Barcode Detected - Error - Open Web Page? Add Contact? + Barcode Detected Compose E-mail? - Look Up Barcode Online? Dial Number? - View In Google Maps? + Error + Keyboard Shortcut Help + Look Up Barcode Online? + Open Web Page? + View In Google Maps?