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 31966552d..66ab2b9d7 100644 --- a/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java +++ b/android-m3/src/com/google/zxing/client/android/BarcodeReaderCaptureActivity.java @@ -61,7 +61,7 @@ public final class BarcodeReaderCaptureActivity extends Activity { cameraManager = new CameraManager(getApplication()); surfaceView = new CameraSurfaceView(getApplication(), cameraManager); setContentView(surfaceView); - workerThread = new WorkerThread(surfaceView, cameraManager, messageHandler); + workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler); workerThread.requestPreviewLoop(); workerThread.start(); @@ -82,7 +82,7 @@ public final class BarcodeReaderCaptureActivity extends Activity { super.onResume(); cameraManager.openDriver(); if (workerThread == null) { - workerThread = new WorkerThread(surfaceView, cameraManager, messageHandler); + workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler); workerThread.requestPreviewLoop(); workerThread.start(); } @@ -102,10 +102,16 @@ public final class BarcodeReaderCaptureActivity extends Activity { public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { workerThread.requestStillAndDecode(); - return true; + } else if (keyCode == KeyEvent.KEYCODE_Q) { + workerThread.requestStillAndDecodeQR(); + } else if (keyCode == KeyEvent.KEYCODE_U) { + workerThread.requestStillAndDecode1D(); + } else if (keyCode == KeyEvent.KEYCODE_C) { + workerThread.requestStillAndSave(); } else { return super.onKeyDown(keyCode, event); } + return true; } @Override diff --git a/android-m3/src/com/google/zxing/client/android/WorkerThread.java b/android-m3/src/com/google/zxing/client/android/WorkerThread.java index c0f6b3541..012bd1c4f 100644 --- a/android-m3/src/com/google/zxing/client/android/WorkerThread.java +++ b/android-m3/src/com/google/zxing/client/android/WorkerThread.java @@ -16,14 +16,25 @@ 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. @@ -32,6 +43,7 @@ import com.google.zxing.Result; */ final class WorkerThread extends Thread { + private final BarcodeReaderCaptureActivity activity; private final CameraSurfaceView surfaceView; private final CameraManager cameraManager; private final Handler handler; @@ -42,10 +54,15 @@ final class WorkerThread extends Thread { IDLE, PREVIEW_LOOP, STILL_AND_DECODE, + STILL_AND_DECODE_1D, + STILL_AND_DECODE_QR, + STILL_AND_SAVE, DONE } - WorkerThread(CameraSurfaceView surfaceView, CameraManager cameraManager, Handler handler) { + WorkerThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView, + CameraManager cameraManager, Handler handler) { + this.activity = activity; this.surfaceView = surfaceView; this.cameraManager = cameraManager; this.handler = handler; @@ -64,20 +81,33 @@ final class WorkerThread extends Thread { 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; + 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; @@ -95,6 +125,21 @@ final class WorkerThread extends Thread { 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(); @@ -109,8 +154,8 @@ final class WorkerThread extends Thread { synchronized (idleLock) { idleLock.wait(); } - } catch (InterruptedException ie) { - // continue + } catch (InterruptedException e) { + // Continue } } @@ -120,4 +165,74 @@ final class WorkerThread extends Thread { } } + 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"; + } + }