package com.google.zxing.client.android;
-import android.util.TypedValue;
-import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.camera.CameraManager;
import com.google.zxing.client.android.history.HistoryManager;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Toast;
import java.io.IOException;
import java.text.DateFormat;
-import java.util.Arrays;
-import java.util.List;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.Vector;
-import java.util.regex.Pattern;
/**
* 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 Sean Owen
*/
public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
private static final String TAG = CaptureActivity.class.getSimpleName();
- private static final Pattern COMMA_PATTERN = Pattern.compile(",");
-
private static final int SHARE_ID = Menu.FIRST;
private static final int HISTORY_ID = Menu.FIRST + 1;
private static final int SETTINGS_ID = Menu.FIRST + 2;
private static final int ABOUT_ID = Menu.FIRST + 4;
private static final long INTENT_RESULT_DURATION = 1500L;
+ private static final long BULK_MODE_SCAN_DELAY_MS = 1000L;
private static final float BEEP_VOLUME = 0.10f;
private static final long VIBRATE_DURATION = 200L;
private static final String RETURN_CODE_PLACEHOLDER = "{CODE}";
private static final String RETURN_URL_PARAM = "ret";
- static final Vector<BarcodeFormat> PRODUCT_FORMATS;
- static final Vector<BarcodeFormat> ONE_D_FORMATS;
- static final Vector<BarcodeFormat> QR_CODE_FORMATS;
- static final Vector<BarcodeFormat> ALL_FORMATS;
-
+ private static final Set<ResultMetadataType> DISPLAYABLE_METADATA_TYPES;
static {
- PRODUCT_FORMATS = new Vector<BarcodeFormat>(5);
- PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);
- PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);
- PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
- PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);
- PRODUCT_FORMATS.add(BarcodeFormat.RSS14);
- ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 3);
- ONE_D_FORMATS.addAll(PRODUCT_FORMATS);
- ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
- ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
- ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
- ONE_D_FORMATS.add(BarcodeFormat.ITF);
- QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);
- QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
- ALL_FORMATS = new Vector<BarcodeFormat>(ONE_D_FORMATS.size() + QR_CODE_FORMATS.size());
- ALL_FORMATS.addAll(ONE_D_FORMATS);
- ALL_FORMATS.addAll(QR_CODE_FORMATS);
+ DISPLAYABLE_METADATA_TYPES = new HashSet<ResultMetadataType>(5);
+ DISPLAYABLE_METADATA_TYPES.add(ResultMetadataType.ISSUE_NUMBER);
+ DISPLAYABLE_METADATA_TYPES.add(ResultMetadataType.SUGGESTED_PRICE);
+ DISPLAYABLE_METADATA_TYPES.add(ResultMetadataType.ERROR_CORRECTION_LEVEL);
+ DISPLAYABLE_METADATA_TYPES.add(ResultMetadataType.POSSIBLE_COUNTRY);
}
private enum Source {
private CaptureActivityHandler handler;
private ViewfinderView viewfinderView;
+ private TextView statusView;
private View resultView;
private MediaPlayer mediaPlayer;
private Result lastResult;
private String characterSet;
private String versionName;
private HistoryManager historyManager;
- private boolean firstLaunch;
+ private InactivityTimer inactivityTimer;
- private final OnCompletionListener beepListener = new BeepListener();
+ /**
+ * When the beep has finished playing, rewind to queue up another one.
+ */
+ private final OnCompletionListener beepListener = new OnCompletionListener() {
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ mediaPlayer.seekTo(0);
+ }
+ };
private final DialogInterface.OnClickListener aboutListener =
new DialogInterface.OnClickListener() {
CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
resultView = findViewById(R.id.result_view);
+ statusView = (TextView) findViewById(R.id.status_view);
handler = null;
lastResult = null;
hasSurface = false;
historyManager = new HistoryManager(this);
historyManager.trimHistory();
+ inactivityTimer = new InactivityTimer(this);
- firstLaunch = showHelpOnFirstLaunch();
+ showHelpOnFirstLaunch();
}
@Override
protected void onResume() {
super.onResume();
+ resetStatusView();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (action.equals(Intents.Scan.ACTION)) {
// Scan the formats the intent requested, and return the result to the calling activity.
source = Source.NATIVE_APP_INTENT;
- decodeFormats = parseDecodeFormats(intent);
- resetStatusView();
+ decodeFormats = DecodeFormatManager.parseDecodeFormats(intent);
} else if (dataString != null && dataString.contains(PRODUCT_SEARCH_URL_PREFIX) &&
dataString.contains(PRODUCT_SEARCH_URL_SUFFIX)) {
// Scan only products and send the result to mobile Product Search.
source = Source.PRODUCT_SEARCH_LINK;
sourceUrl = dataString;
- decodeFormats = PRODUCT_FORMATS;
- resetStatusView();
+ decodeFormats = DecodeFormatManager.PRODUCT_FORMATS;
} else if (dataString != null && dataString.startsWith(ZXING_URL)) {
// Scan formats requested in query string (all formats if none specified).
- // If a return URL is specified, send the results there. Otherwise, handle the results ourselves.
+ // If a return URL is specified, send the results there. Otherwise, handle it ourselves.
source = Source.ZXING_LINK;
sourceUrl = dataString;
Uri inputUri = Uri.parse(sourceUrl);
returnUrlTemplate = inputUri.getQueryParameter(RETURN_URL_PARAM);
- decodeFormats = parseDecodeFormats(inputUri);
- resetStatusView();
+ decodeFormats = DecodeFormatManager.parseDecodeFormats(inputUri);
} else {
// Scan all formats and handle the results ourselves (launched from Home).
source = Source.NONE;
decodeFormats = null;
- resetStatusView();
}
characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET);
} else {
source = Source.NONE;
decodeFormats = null;
characterSet = null;
- if (lastResult == null) {
- resetStatusView();
- }
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
vibrate = prefs.getBoolean(PreferencesActivity.KEY_VIBRATE, false);
copyToClipboard = prefs.getBoolean(PreferencesActivity.KEY_COPY_TO_CLIPBOARD, true);
initBeepSound();
-
- if (!firstLaunch) {
- Toast.makeText(this, R.string.msg_default_status, Toast.LENGTH_SHORT).show();
- }
- }
-
- private static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {
- List<String> scanFormats = null;
- String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);
- if (scanFormatsString != null) {
- scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
- }
- return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
- }
-
- private static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) {
- List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS);
- if (formats != null && formats.size() == 1 && formats.get(0) != null){
- formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));
- }
- return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));
- }
-
- private static Vector<BarcodeFormat> parseDecodeFormats(List<String> scanFormats,
- String decodeMode) {
- if (scanFormats != null) {
- Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
- try {
- for (String format : scanFormats) {
- formats.add(BarcodeFormat.valueOf(format));
- }
- return formats;
- } catch (IllegalArgumentException iae) {
- // ignore it then
- }
- }
- if (decodeMode != null) {
- if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {
- return PRODUCT_FORMATS;
- }
- if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {
- return QR_CODE_FORMATS;
- }
- if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {
- return ONE_D_FORMATS;
- }
- }
- return null;
}
@Override
CameraManager.get().closeDriver();
}
+ @Override
+ protected void onDestroy() {
+ inactivityTimer.shutdown();
+ super.onDestroy();
+ }
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
* @param barcode A greyscale bitmap of the camera data which was decoded.
*/
public void handleDecode(Result rawResult, Bitmap barcode) {
+ inactivityTimer.onActivity();
lastResult = rawResult;
historyManager.addHistoryItem(rawResult);
if (barcode == null) {
handleDecodeExternally(rawResult, barcode);
break;
case ZXING_LINK:
- if(returnUrlTemplate == null){
+ if (returnUrlTemplate == null){
handleDecodeInternally(rawResult, barcode);
} else {
handleDecodeExternally(rawResult, barcode);
}
break;
case NONE:
- handleDecodeInternally(rawResult, barcode);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ if (prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) {
+ Toast.makeText(this, R.string.msg_bulk_mode_scanned, Toast.LENGTH_SHORT).show();
+ // Wait a moment or else it will scan the same barcode continuously about 3 times
+ if (handler != null) {
+ handler.sendEmptyMessageDelayed(R.id.restart_preview, BULK_MODE_SCAN_DELAY_MS);
+ }
+ resetStatusView();
+ } else {
+ handleDecodeInternally(rawResult, barcode);
+ }
break;
}
}
paint.setColor(getResources().getColor(R.color.result_points));
if (points.length == 2) {
paint.setStrokeWidth(4.0f);
- canvas.drawLine(points[0].getX(), points[0].getY(), points[1].getX(),
- points[1].getY(), paint);
+ drawLine(canvas, paint, points[0], points[1]);
+ } else if (points.length == 4 &&
+ (rawResult.getBarcodeFormat().equals(BarcodeFormat.UPC_A)) ||
+ (rawResult.getBarcodeFormat().equals(BarcodeFormat.EAN_13))) {
+ // Hacky special case -- draw two lines, for the barcode and metadata
+ drawLine(canvas, paint, points[0], points[1]);
+ drawLine(canvas, paint, points[2], points[3]);
} else {
paint.setStrokeWidth(10.0f);
for (ResultPoint point : points) {
}
}
+ private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b) {
+ canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), paint);
+ }
+
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult, Bitmap barcode) {
+ statusView.setVisibility(View.GONE);
viewfinderView.setVisibility(View.GONE);
resultView.setVisibility(View.VISIBLE);
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
if (barcode == null) {
- barcodeImageView.setImageResource(R.drawable.launcher_icon_large);
+ barcodeImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
+ R.drawable.launcher_icon));
} else {
barcodeImageView.setImageBitmap(barcode);
}
- barcodeImageView.setVisibility(View.VISIBLE);
TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
- formatTextView.setVisibility(View.VISIBLE);
formatTextView.setText(rawResult.getBarcodeFormat().toString());
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
- typeTextView.setVisibility(View.VISIBLE);
typeTextView.setText(resultHandler.getType().toString());
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
String formattedTime = formatter.format(new Date(rawResult.getTimestamp()));
TextView timeTextView = (TextView) findViewById(R.id.time_text_view);
- timeTextView.setVisibility(View.VISIBLE);
timeTextView.setText(formattedTime);
+
+ TextView metaTextView = (TextView) findViewById(R.id.meta_text_view);
+ View metaTextViewLabel = findViewById(R.id.meta_text_view_label);
+ metaTextView.setVisibility(View.GONE);
+ metaTextViewLabel.setVisibility(View.GONE);
+ Map<ResultMetadataType,Object> metadata =
+ (Map<ResultMetadataType,Object>) rawResult.getResultMetadata();
+ if (metadata != null) {
+ StringBuilder metadataText = new StringBuilder(20);
+ for (Map.Entry<ResultMetadataType,Object> entry : metadata.entrySet()) {
+ if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) {
+ metadataText.append(entry.getValue()).append('\n');
+ }
+ }
+ if (metadataText.length() > 0) {
+ metadataText.setLength(metadataText.length() - 1);
+ metaTextView.setText(metadataText);
+ metaTextView.setVisibility(View.VISIBLE);
+ metaTextViewLabel.setVisibility(View.VISIBLE);
+ }
+ }
+
TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
CharSequence displayContents = resultHandler.getDisplayContents();
contentsTextView.setText(displayContents);
- // Crudely scale betweeen 22 and 42 -- bigger font for shorter text
- int scaledSize = Math.max(22, 42 - displayContents.length() / 4);
+ // Crudely scale betweeen 22 and 32 -- bigger font for shorter text
+ int scaledSize = Math.max(22, 32 - displayContents.length() / 4);
contentsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, scaledSize);
int buttonCount = resultHandler.getButtonCount();
// barcode was found (e.g. contact info) rather than the full contents, which they won't
// have time to read.
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
- Toast.makeText(this, resultHandler.getDisplayTitle(), Toast.LENGTH_SHORT).show();
+ statusView.setText(getString(resultHandler.getDisplayTitle()));
if (copyToClipboard) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
// Replace each occurrence of RETURN_CODE_PLACEHOLDER in the returnUrlTemplate
// with the scanned code. This allows both queries and REST-style URLs to work.
Message message = Message.obtain(handler, R.id.launch_product_query);
- message.obj = returnUrlTemplate.replace(RETURN_CODE_PLACEHOLDER, resultHandler.getDisplayContents().toString());
+ message.obj = returnUrlTemplate.replace(RETURN_CODE_PLACEHOLDER,
+ resultHandler.getDisplayContents().toString());
handler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
}
}
return;
}
if (handler == null) {
- boolean beginScanning = lastResult == null;
- handler = new CaptureActivityHandler(this, decodeFormats, characterSet, beginScanning);
+ handler = new CaptureActivityHandler(this, decodeFormats, characterSet);
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.app_name));
builder.setMessage(getString(R.string.msg_camera_framework_bug));
- builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialogInterface, int i) {
- finish();
- }
- });
+ builder.setPositiveButton(R.string.button_ok, new FinishListener(this));
+ builder.setOnCancelListener(new FinishListener(this));
builder.show();
}
private void resetStatusView() {
resultView.setVisibility(View.GONE);
+ statusView.setText(R.string.msg_default_status);
+ statusView.setVisibility(View.VISIBLE);
viewfinderView.setVisibility(View.VISIBLE);
lastResult = null;
}
public void drawViewfinder() {
viewfinderView.drawViewfinder();
}
-
- /**
- * When the beep has finished playing, rewind to queue up another one.
- */
- private static class BeepListener implements OnCompletionListener {
- public void onCompletion(MediaPlayer mediaPlayer) {
- mediaPlayer.seekTo(0);
- }
- }
}