<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.android">
<application android:icon="@drawable/icon">
- <activity android:name=".BarcodeReaderCaptureActivity" android:label="@string/app_name">
+ <activity android:name="BarcodeReaderCaptureActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<target name="init">
<tstamp/>
- <fail message="Please set 'android-home' in build.properties">
+ <fail message="Please set 'android-m5-home' in build.properties">
<condition>
<not>
<available file="${android-home}" type="dir"/>
<target name="optimize" depends="compile" unless="debug">
<jar basedir="${outdir-classes}" destfile="temp.jar"/>
<java jar="${WTK-home}/bin/proguard.jar" fork="true" failonerror="true">
- <jvmarg value="-Dmaximum.inlined.code.length=32"/>
- <arg value="-injars temp.jar"/>
- <arg value="-outjars optimized.jar"/>
- <arg value="-libraryjars ${android-jar}"/>
- <arg value="-dontpreverify"/>
- <arg value="-dontobfuscate"/>
- <arg value="-keep public class com.google.zxing.client.android.BarcodeReaderCaptureActivity"/>
- <arg value="-optimizationpasses 7"/>
- <arg value="-overloadaggressively"/>
- <arg value="-verbose"/>
- </java>
- <delete file="temp.jar"/>
- <delete dir="${outdir-classes}"/>
- <mkdir dir="${outdir-classes}"/>
- <unzip src="optimized.jar" dest="${outdir-classes}"/>
- <delete file="optimized.jar"/>
+ <jvmarg value="-Dmaximum.inlined.code.length=32"/>
+ <arg value="-injars temp.jar"/>
+ <arg value="-outjars optimized.jar"/>
+ <arg value="-libraryjars ${android-jar}"/>
+ <arg value="-dontpreverify"/>
+ <arg value="-dontobfuscate"/>
+ <!-- Temporary workaround to keep important stuff in the client, while inlining core. -->
+ <arg value="-keep class com.google.zxing.client.android.BarcodeReaderCaptureActivity { *; }"/>
+ <arg value="-keep class com.google.zxing.client.android.CameraManager { *; }"/>
+ <arg value="-keep class com.google.zxing.client.android.CameraThread { *; }"/>
+ <arg value="-optimizationpasses 7"/>
+ <arg value="-overloadaggressively"/>
+ <arg value="-verbose"/>
+ <!-- Needed to allow getters which refer to private members to be inlined. -->
+ <arg value="-allowaccessmodification"/>
+ </java>
+ <delete file="temp.jar"/>
+ <delete dir="${outdir-classes}"/>
+ <mkdir dir="${outdir-classes}"/>
+ <unzip src="optimized.jar" dest="${outdir-classes}"/>
+ <delete file="optimized.jar"/>
</target>
<!-- Convert this project's .class files into .dex files. -->
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
+ android:layout_height="fill_parent">
+ <FrameLayout android:id="@+id/preview_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="#00000000">
+
+ <FrameLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:background="#00000000"/>
+
+ <LinearLayout android:id="@+id/status_view"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:background="#55000000"
+ android:baselineAligned="false"
+ android:padding="4px">
+
+ <TextView android:id="@+id/status_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:layout_weight="1"
+ android:text="@string/msg_default_status"
+ android:textColor="#ffffff"/>
+
+ <Button android:id="@+id/status_action_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center|fill_horizontal"
+ android:visibility="gone"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</FrameLayout>
limitations under the License.
-->
<resources>
- <item type="id" name="decoding_succeeded_message"/>
- <item type="id" name="decoding_failed_message"/>
+ <!-- Messages IDs -->
+ <item type="id" name="preview"/>
+ <item type="id" name="decode"/>
+ <item type="id" name="save"/>
+ <item type="id" name="restart_preview"/>
+ <item type="id" name="quit"/>
+ <item type="id" name="set_decode_all_mode"/>
+ <item type="id" name="set_decode_1D_mode"/>
+ <item type="id" name="set_decode_QR_mode"/>
+ <item type="id" name="toggle_tracing"/>
+ <item type="id" name="decode_started"/>
+ <item type="id" name="decode_succeeded"/>
+ <item type="id" name="decode_failed"/>
+ <item type="id" name="save_succeeded"/>
+ <item type="id" name="save_failed"/>
+
+ <!-- Objects in the view hierarchy -->
+ <item type="id" name="preview_view"/>
+ <item type="id" name="status_action_button"/>
+ <item type="id" name="status_text_view"/>
+ <item type="id" name="status_view"/>
</resources>
import android.os.Message;
import android.view.KeyEvent;
import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
+import android.widget.Button;
+import android.widget.TextView;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.result.ParsedReaderResult;
private CameraManager cameraManager;
private CameraSurfaceView surfaceView;
- private WorkerThread workerThread;
+ private CameraThread cameraThread;
+ private String lastResult;
private static final int ABOUT_ID = Menu.FIRST;
+ private static final int HELP_ID = Menu.FIRST + 1;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.main);
+
cameraManager = new CameraManager(getApplication());
surfaceView = new CameraSurfaceView(getApplication(), cameraManager);
- setContentView(surfaceView);
- workerThread = new WorkerThread(surfaceView, cameraManager, messageHandler);
- workerThread.requestPreviewLoop();
- workerThread.start();
+ surfaceView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+
+ ViewGroup previewView = (ViewGroup) findViewById(R.id.preview_view);
+ previewView.addView(surfaceView);
+ cameraThread = null;
// TODO re-enable this when issues with Matrix.setPolyToPoly() are resolved
//GridSampler.setGridSampler(new AndroidGraphicsGridSampler());
@Override
protected void onResume() {
super.onResume();
+ resetStatusView();
cameraManager.openDriver();
- if (workerThread == null) {
- workerThread = new WorkerThread(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) {
+ cameraThread.quitSynchronously();
+ cameraThread = null;
}
cameraManager.closeDriver();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
- workerThread.requestStillAndDecode();
- return true;
+ if (keyCode == KeyEvent.KEYCODE_A) {
+ cameraThread.setDecodeAllMode();
+ } else if (keyCode == KeyEvent.KEYCODE_C) {
+ 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_T) {
+ cameraThread.toggleTracing();
+ } else if (keyCode == KeyEvent.KEYCODE_U) {
+ cameraThread.setDecode1DMode();
} else {
return super.onKeyDown(keyCode, event);
}
+ return true;
}
@Override
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),
- 0,
+ showAlert(context.getString(R.string.title_about), 0,
context.getString(R.string.msg_about),
context.getString(R.string.button_ok),
true);
break;
+ case HELP_ID:
+ showAlert(context.getString(R.string.title_help), 0,
+ context.getString(R.string.msg_help),
+ context.getString(R.string.button_ok), true);
+ break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case R.id.decoding_succeeded_message:
- handleDecode((Result) message.obj);
+ case R.id.decode_succeeded:
+ int duration = message.arg1;
+ handleDecode((Result) message.obj, duration);
break;
- case R.id.decoding_failed_message:
- Context context = getApplication();
- showAlert(
- context.getString(R.string.title_no_barcode_detected),
- 0,
- context.getString(R.string.msg_no_barcode_detected),
- context.getString(R.string.button_ok),
- true);
+ case R.id.restart_preview:
+ restartPreview();
break;
}
}
};
public void restartPreview() {
- workerThread.requestPreviewLoop();
+ Message restart = Message.obtain(cameraThread.handler, R.id.restart_preview);
+ restart.sendToTarget();
}
- private void handleDecode(Result rawResult) {
- ResultPoint[] points = rawResult.getResultPoints();
- if (points != null && points.length > 0) {
- surfaceView.drawResultPoints(points);
- }
+ private void handleDecode(Result rawResult, int duration) {
+ if (!rawResult.toString().equals(lastResult)) {
+ lastResult = rawResult.toString();
- 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
- showAlert(
- context.getString(getDialogTitleID(readerResult.getType())),
- 0,
- readerResult.getDisplayResult(),
- context.getString(R.string.button_yes),
- handler,
- context.getString(R.string.button_no),
- null,
- true,
- null
- );
+ ResultPoint[] points = rawResult.getResultPoints();
+ if (points != null && points.length > 0) {
+ surfaceView.drawResultPoints(points);
+ }
+
+ TextView textView = (TextView) findViewById(R.id.status_text_view);
+ ParsedReaderResult readerResult = parseReaderResult(rawResult);
+ textView.setText(readerResult.getDisplayResult() + " (" + duration + " ms)");
+
+ Button actionButton = (Button) findViewById(R.id.status_action_button);
+ int buttonText = getActionButtonText(readerResult.getType());
+ if (buttonText != 0) {
+ actionButton.setVisibility(View.VISIBLE);
+ actionButton.setText(buttonText);
+ ResultHandler handler = new ResultHandler(this, readerResult);
+ actionButton.setOnClickListener(handler);
+ actionButton.requestFocus();
+ } else {
+ actionButton.setVisibility(View.GONE);
+ }
+ // Show the green finder patterns for one second, then restart the preview
+ Message message = Message.obtain(messageHandler, R.id.restart_preview);
+ messageHandler.sendMessageDelayed(message, 1000);
} else {
- // Just show information to user
- showAlert(
- context.getString(R.string.title_barcode_detected),
- 0,
- readerResult.getDisplayResult(),
- context.getString(R.string.button_ok),
- true);
+ restartPreview();
}
}
+ private void resetStatusView() {
+ TextView textView = (TextView) findViewById(R.id.status_text_view);
+ textView.setText(R.string.msg_default_status);
+ Button actionButton = (Button) findViewById(R.id.status_action_button);
+ actionButton.setVisibility(View.GONE);
+ lastResult = "";
+ }
+
private static ParsedReaderResult parseReaderResult(Result rawResult) {
ParsedReaderResult readerResult = ParsedReaderResult.parseReaderResult(rawResult);
if (readerResult.getType().equals(ParsedReaderResultType.TEXT)) {
return readerResult;
}
- private static int getDialogTitleID(ParsedReaderResultType type) {
+ private static int getActionButtonText(ParsedReaderResultType type) {
+ int buttonText;
if (type.equals(ParsedReaderResultType.ADDRESSBOOK)) {
- return R.string.title_add_contact;
+ buttonText = R.string.button_add_contact;
} else if (type.equals(ParsedReaderResultType.URI) ||
type.equals(ParsedReaderResultType.BOOKMARK) ||
type.equals(ParsedReaderResultType.URLTO)) {
- return R.string.title_open_url;
+ buttonText = R.string.button_open_browser;
} else if (type.equals(ParsedReaderResultType.EMAIL) ||
type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) {
- return R.string.title_compose_email;
+ buttonText = R.string.button_email;
} else if (type.equals(ParsedReaderResultType.UPC)) {
- return R.string.title_lookup_barcode;
+ buttonText = R.string.button_lookup_product;
} else if (type.equals(ParsedReaderResultType.TEL)) {
- return R.string.title_dial;
+ buttonText = R.string.button_dial;
} else if (type.equals(ParsedReaderResultType.GEO)) {
- return R.string.title_view_maps;
+ buttonText = R.string.button_show_map;
} else {
- return R.string.title_barcode_detected;
+ buttonText = 0;
}
+ return buttonText;
}
}
\ No newline at end of file
private final Context context;
private Point cameraResolution;
private Point stillResolution;
+ private Point previewResolution;
private int stillMultiplier;
private Point screenResolution;
private Rect framingRect;
- private final Bitmap bitmap;
+ private Bitmap bitmap;
// TODO switch back to CameraDevice later
// private CameraDevice camera;
private CameraSource cameraSource;
// end TODO
private final CameraDevice.CaptureParams params;
private boolean previewMode;
+ private boolean usePreviewForDecode;
CameraManager(Context context) {
this.context = context;
- calculateStillResolution();
getScreenResolution();
- bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false);
+ calculateStillResolution();
+ calculatePreviewResolution();
+
+ usePreviewForDecode = true;
+ setUsePreviewForDecode(false);
+
// TODO switch back to CameraDevice later
- // camera = CameraDevice.open();
+ // camera = null;
Bitmap fakeBitmap = BitmapFactory.decodeFile("/tmp/barcode.jpg");
if (fakeBitmap == null) {
throw new RuntimeException("/tmp/barcode.jpg was not found");
}
cameraSource = new BitmapCamera(fakeBitmap, stillResolution.x, stillResolution.y);
// end TODO
+
params = new CameraDevice.CaptureParams();
- previewMode = false;
- setPreviewMode(true);
}
public void openDriver() {
- // TODO switch back to CameraDevice later
- // if (camera == null) {
- // camera = CameraDevice.open();
- // }
- // end TODO
+// TODO switch back to CameraDevice later
+// if (camera == null) {
+// camera = CameraDevice.open();
+// // If we're reopening the camera, we need to reset the capture params.
+// previewMode = false;
+// setPreviewMode(true);
+// }
+// end TODO
}
public void closeDriver() {
}
public Bitmap captureStill() {
- setPreviewMode(false);
+ setPreviewMode(usePreviewForDecode);
Canvas canvas = new Canvas(bitmap);
// TODO switch back to CameraDevice later
// 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: " + stillResolution.x + "," +
+ stillResolution.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
*/
public Rect getFramingRect() {
if (framingRect == null) {
- int size = stillResolution.x * screenResolution.x / cameraResolution.x;
+ int size = stillResolution.x * screenResolution.x / previewResolution.x;
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;
}
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;
}
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.srcWidth = previewResolution.x;
+ params.srcHeight = previewResolution.y;
+ params.leftPixel = (cameraResolution.x - params.srcWidth) / 2;
+ params.topPixel = (cameraResolution.y - params.srcHeight) / 2;
params.outputWidth = screenResolution.x;
params.outputHeight = screenResolution.y;
params.dataFormat = 2; // RGB565
" nativeResolution " + nativeResolution + " stillMultiplier " + stillMultiplier);
}
+ /**
+ * The goal of the preview resolution is to show a little context around the framing rectangle
+ * which is the actual captured area in still mode.
+ */
+ private void calculatePreviewResolution() {
+ if (previewResolution == null) {
+ int previewHeight = (int) (stillResolution.x * stillMultiplier * 1.8f);
+ int previewWidth = previewHeight * screenResolution.x / screenResolution.y;
+ previewWidth = ((previewWidth + 7) >> 3) << 3;
+ if (previewWidth > cameraResolution.x) previewWidth = cameraResolution.x;
+ previewHeight = previewWidth * screenResolution.y / screenResolution.x;
+ previewResolution = new Point(previewWidth, previewHeight);
+ Log.v(TAG, "previewWidth " + previewWidth + " previewHeight " + previewHeight);
+ }
+ }
+
// 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.
--- /dev/null
+/*
+ * 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 State state;
+
+ private enum State {
+ PREVIEW,
+ DECODE,
+ SAVE,
+ DONE
+ }
+
+ CameraThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView,
+ CameraManager cameraManager, Handler activityHandler) {
+ this.surfaceView = surfaceView;
+ this.activityHandler = activityHandler;
+
+ decodeThread = new DecodeThread(activity, cameraManager);
+ decodeThread.start();
+ state = State.DONE;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ handler = new Handler() {
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case R.id.preview:
+ if (state == State.PREVIEW) {
+ surfaceView.capturePreviewAndDraw();
+ }
+ break;
+ case R.id.save:
+ state = State.SAVE;
+ Message save = Message.obtain(decodeThread.handler, R.id.save);
+ save.sendToTarget();
+ break;
+ case R.id.restart_preview:
+ restartPreviewAndDecode();
+ break;
+ case R.id.quit:
+ state = State.DONE;
+ Message quit = Message.obtain(decodeThread.handler, R.id.quit);
+ quit.sendToTarget();
+ try {
+ decodeThread.join();
+ } catch (InterruptedException e) {
+ }
+ Looper.myLooper().quit();
+ break;
+ case R.id.decode_started:
+ // Since the decoder is done with the camera, continue fetching preview frames.
+ state = State.PREVIEW;
+ break;
+ case R.id.decode_succeeded:
+ state = State.DONE;
+ // 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();
+ break;
+ case R.id.decode_failed:
+ // We're decoding as fast as possible, so when one fails, start another.
+ startDecode();
+ 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();
+ break;
+ }
+
+ if (state == State.PREVIEW) {
+ Message preview = Message.obtain(handler, R.id.preview);
+ preview.sendToTarget();
+ }
+ }
+ };
+ decodeThread.setCameraThreadHandler(handler);
+
+ // Start ourselves capturing previews
+ restartPreviewAndDecode();
+ Looper.loop();
+ }
+
+ public void quitSynchronously() {
+ Message quit = Message.obtain(handler, R.id.quit);
+ quit.sendToTarget();
+ try {
+ join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ 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();
+ }
+
+ public void toggleTracing() {
+ Message message = Message.obtain(decodeThread.handler, R.id.toggle_tracing);
+ message.sendToTarget();
+ }
+
+ /**
+ * Start a decode if possible, but not now if the DecodeThread is in the middle of saving.
+ */
+ private void startDecode() {
+ if (state != State.SAVE) {
+ state = State.DECODE;
+ Message decode = Message.obtain(decodeThread.handler, R.id.decode);
+ decode.sendToTarget();
+ }
+ }
+
+ /**
+ * Take one preview to update the screen, then do a decode and continue previews.
+ */
+ private void restartPreviewAndDecode() {
+ state = State.PREVIEW;
+ surfaceView.capturePreviewAndDraw();
+ startDecode();
+ }
+
+}
--- /dev/null
+/*
+ * 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.Debug;
+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<DecodeHintType, Object> hints;
+ private Handler cameraThreadHandler;
+ private int methodTraceCount;
+ private boolean tracing;
+
+ DecodeThread(BarcodeReaderCaptureActivity activity, CameraManager cameraManager) {
+ this.activity = activity;
+ this.cameraManager = cameraManager;
+ methodTraceCount = 0;
+ tracing = false;
+ }
+
+ @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;
+ case R.id.toggle_tracing:
+ tracing = !tracing;
+ 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<DecodeHintType, Object>(3);
+ Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
+ 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<DecodeHintType, Object>(3);
+ Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
+ 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();
+
+ if (tracing) {
+ Debug.startMethodTracing("/sdcard/ZXingDecodeThread" + methodTraceCount);
+ methodTraceCount++;
+ }
+ boolean success;
+ Result rawResult = null;
+ try {
+ MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap);
+ rawResult = new MultiFormatReader().decode(source, hints);
+ success = true;
+ } catch (ReaderException e) {
+ success = false;
+ }
+ if (tracing) {
+ Debug.stopMethodTracing();
+ }
+ Date endDate = new Date();
+
+ if (success) {
+ Message message = Message.obtain(cameraThreadHandler, R.id.decode_succeeded, rawResult);
+ message.arg1 = (int) (endDate.getTime() - startDate.getTime());
+ message.sendToTarget();
+ } else {
+ Message message = Message.obtain(cameraThreadHandler, R.id.decode_failed);
+ message.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";
+ }
+
+}
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)
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);
+ image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1);
+
+ // If the current decoder calculated the blackPoint based on one row, assume we're trying to
+ // decode a 1D barcode, and apply some sharpening.
+ // TODO: We may want to add a fifth parameter to request the amount of shapening to be done.
+ if (lastMethod == BlackPointEstimationMethod.ROW_SAMPLING) {
+ int left = computeRGBLuminance(pixelRow[0]);
+ int center = computeRGBLuminance(pixelRow[1]);
+ for (int i = 1; i < getWidth - 1; i++) {
+ int right = computeRGBLuminance(pixelRow[i + 1]);
+ // Simple -1 4 -1 box filter with a weight of 2
+ int luminance = ((center << 2) - left - right) >> 1;
+ if (luminance < blackPoint) {
+ row.set(i);
+ }
+ left = center;
+ center = right;
+ }
+ } else {
+ for (int i = 0; i < getWidth; i++) {
+ if (computeRGBLuminance(pixelRow[i]) < blackPoint) {
+ row.set(i);
+ }
}
}
return row;
/**
* 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
package com.google.zxing.client.android;
-import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
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 android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import com.google.zxing.client.result.*;
/**
* Handles the result of barcode decoding in the context of the Android platform,
- * by dispatching the proper intents and so on.
+ * by dispatching the proper intents to open other activities like GMail, Maps, etc.
*
* @author srowen@google.com (Sean Owen)
* @author dswitkin@google.com (Daniel Switkin)
*/
-final class ResultHandler implements DialogInterface.OnClickListener {
+final class ResultHandler implements Button.OnClickListener {
+
+ private static final String TAG = "ResultHandler";
private final Intent intent;
private final BarcodeReaderCaptureActivity captureActivity;
return intent;
}
- public void onClick(DialogInterface dialogInterface, int i) {
- if (i == DialogInterface.BUTTON1) {
- if (intent != null) {
- captureActivity.startActivity(intent);
- }
- } else {
- captureActivity.restartPreview();
+ public void onClick(View view) {
+ if (intent != null) {
+ captureActivity.startActivity(intent);
}
}
+++ /dev/null
-/*
- * 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();
- }
- }
-
-}
-->
<resources>
<string name="app_name">Barcode Reader</string>
- <string name="button_no">No</string>
+ <string name="button_add_contact">Add Contact</string>
+ <string name="button_dial">Dial</string>
+ <string name="button_email">Email</string>
+ <string name="button_lookup_product">Lookup Product</string>
<string name="button_ok">OK</string>
- <string name="button_yes">Yes</string>
+ <string name="button_open_browser">Open Browser</string>
+ <string name="button_show_map">Show Map</string>
<string name="menu_about">About...</string>
+ <string name="menu_help">Help...</string>
<string name="msg_about">ZXing Barcode Reader v@VERSION@\nhttp://code.google.com/p/zxing</string>
- <string name="msg_no_barcode_detected">Sorry, no barcode was found.</string>
+ <string name="msg_help">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\nT: Toggle debug method tracing\nU: Decode only UPC/1D barcodes</string>
+ <string name="msg_default_status">Place a barcode inside the viewfinder rectangle to read it.</string>
<string name="title_about">About</string>
- <string name="title_barcode_detected">Barcode Detected</string>
- <string name="title_no_barcode_detected">No Barcode Detected</string>
- <string name="title_error">Error</string>
- <string name="title_open_url">Open Web Page?</string>
- <string name="title_add_contact">Add Contact?</string>
- <string name="title_compose_email">Compose E-mail?</string>
- <string name="title_lookup_barcode">Look Up Barcode Online?</string>
- <string name="title_dial">Dial Number?</string>
- <string name="title_view_maps">View In Google Maps?</string>
+ <string name="title_help">Keyboard Shortcut Help</string>
</resources>