diff --git a/glass/.gitignore b/glass/.gitignore
new file mode 100644
index 000000000..8213249e8
--- /dev/null
+++ b/glass/.gitignore
@@ -0,0 +1,6 @@
+target/
+*.iml
+bin/
+gen/
+libs/
+local.properties
diff --git a/glass/AndroidManifest.xml b/glass/AndroidManifest.xml
new file mode 100644
index 000000000..2809c1960
--- /dev/null
+++ b/glass/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/glass/pom.xml b/glass/pom.xml
new file mode 100644
index 000000000..a33e70258
--- /dev/null
+++ b/glass/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ 4.0.0
+
+ glass
+ 0.2.0
+ apk
+
+
+
+ com.google.zxing
+ core
+ ${project.parent.version}
+
+
+ com.google.android
+ android
+
+
+
+
+ com.google.zxing
+ zxing-parent
+ 3.0.2-SNAPSHOT
+
+
+
+ src
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+
+
+
+
+ Android Barcode Scanner app for Google Glass
+ Provides the Google Glass app "Barcode Scanner"
+
+
diff --git a/glass/proguard.cfg b/glass/proguard.cfg
new file mode 100644
index 000000000..3dc25f17f
--- /dev/null
+++ b/glass/proguard.cfg
@@ -0,0 +1,68 @@
+# This is a configuration file for ProGuard.
+# http://proguard.sourceforge.net/index.html#manual/usage.html
+
+# Optimizations: If you don't want to optimize, use the
+# proguard-android.txt configuration file instead of this one, which
+# turns off the optimization flags. Adding optimization introduces
+# certain risks, since for example not all optimizations performed by
+# ProGuard works on all versions of Dalvik. The following flags turn
+# off various optimizations known to have issues, but the list may not
+# be complete or up to date. (The "arithmetic" optimization can be
+# used if you are only targeting Android 2.0 or later.) Make sure you
+# test thoroughly if you go this route.
+-optimizations !code/simplification/cast,!field/*,!class/merging/*,!code/allocation/variable,!method/marking/private
+-optimizationpasses 5
+-allowaccessmodification
+-dontpreverify
+
+# The remainder of this file is identical to the non-optimized version
+# of the Proguard configuration file (except that the other file has
+# flags to turn off optimization).
+
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-verbose
+
+# ADDED
+-dontshrink
+-dontobfuscate
+
+-keepattributes *Annotation*
+-keep public class com.google.vending.licensing.ILicensingService
+-keep public class com.android.vending.licensing.ILicensingService
+
+# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
+-keepclasseswithmembernames class * {
+ native ;
+}
+
+# keep setters in Views so that animations can still work.
+# see http://proguard.sourceforge.net/manual/examples.html#beans
+-keepclassmembers public class * extends android.view.View {
+ void set*(***);
+ *** get*();
+}
+
+# We want to keep methods in Activity that could be used in the XML attribute onClick
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+}
+
+# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
+
+-keepclassmembers class **.R$* {
+ public static ;
+}
+
+# The support library contains references to newer platform versions.
+# Don't warn about those in case this app is linking against an older
+# platform version. We know about them, and they are safe.
+-dontwarn android.support.**
diff --git a/glass/res/drawable-hdpi/ic_launcher.png b/glass/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..5932aadea
Binary files /dev/null and b/glass/res/drawable-hdpi/ic_launcher.png differ
diff --git a/glass/res/layout/capture.xml b/glass/res/layout/capture.xml
new file mode 100644
index 000000000..9b86a20d9
--- /dev/null
+++ b/glass/res/layout/capture.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/glass/res/values/colors.xml b/glass/res/values/colors.xml
new file mode 100755
index 000000000..c1e8ff2ba
--- /dev/null
+++ b/glass/res/values/colors.xml
@@ -0,0 +1,20 @@
+
+
+
+ #ffffffff
+ #b0000000
+
diff --git a/glass/res/values/ids.xml b/glass/res/values/ids.xml
new file mode 100755
index 000000000..c640cab2c
--- /dev/null
+++ b/glass/res/values/ids.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/glass/res/values/strings.xml b/glass/res/values/strings.xml
new file mode 100644
index 000000000..cffbf3f68
--- /dev/null
+++ b/glass/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Barcode Scanner for Glass
+
diff --git a/glass/res/xml/barcode_scanner_show.xml b/glass/res/xml/barcode_scanner_show.xml
new file mode 100644
index 000000000..94ca46b93
--- /dev/null
+++ b/glass/res/xml/barcode_scanner_show.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/glass/src/com/google/zxing/client/glass/CameraConfigurationManager.java b/glass/src/com/google/zxing/client/glass/CameraConfigurationManager.java
new file mode 100644
index 000000000..d343946c0
--- /dev/null
+++ b/glass/src/com/google/zxing/client/glass/CameraConfigurationManager.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * 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.glass;
+
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Sean Owen
+ */
+final class CameraConfigurationManager {
+
+ private static final String TAG = "CameraConfiguration";
+ private static final int AREA_PER_1000 = 400;
+ private static final int MIN_FPS = 10;
+
+ private CameraConfigurationManager() {
+ }
+
+ static void configure(Camera camera) {
+ Camera.Parameters parameters = camera.getParameters();
+ //parameters.setPreviewSize(1024, 768);
+ parameters.setPreviewSize(512, 288);
+ //configureAdvanced(parameters);
+ camera.setParameters(parameters);
+ }
+
+ private static void configureAdvanced(Camera.Parameters parameters) {
+
+ setBestPreviewFPS(parameters);
+
+ String sceneMode = findSettableValue(parameters.getSupportedSceneModes(),
+ Camera.Parameters.SCENE_MODE_BARCODE);
+ if (sceneMode != null) {
+ parameters.setSceneMode(sceneMode);
+ } else {
+ Log.i(TAG, "Scene mode is not supported");
+ }
+
+ if (parameters.isVideoStabilizationSupported()) {
+ Log.i(TAG, "Enabling video stabilization...");
+ parameters.setVideoStabilization(true);
+ } else {
+ Log.i(TAG, "This device does not support video stabilization");
+ }
+
+ if (parameters.getMaxNumMeteringAreas() > 0) {
+ Log.i(TAG, "Old metering areas: " + parameters.getMeteringAreas());
+ List middleArea = Collections.singletonList(
+ new Camera.Area(new Rect(-AREA_PER_1000, -AREA_PER_1000, AREA_PER_1000, AREA_PER_1000), 1));
+ parameters.setMeteringAreas(middleArea);
+ } else {
+ Log.i(TAG, "Device does not support metering areas");
+ }
+
+ if (parameters.isZoomSupported()) {
+ Log.i(TAG, "Setting to max zoom");
+ parameters.setZoom(parameters.getMaxZoom());
+ } else {
+ Log.i(TAG, "Zoom is not supported");
+ }
+
+ }
+
+ private static String findSettableValue(Collection supportedValues,
+ String... desiredValues) {
+ Log.i(TAG, "Supported values: " + supportedValues);
+ String result = null;
+ if (supportedValues != null) {
+ for (String desiredValue : desiredValues) {
+ if (supportedValues.contains(desiredValue)) {
+ result = desiredValue;
+ break;
+ }
+ }
+ }
+ Log.i(TAG, "Settable value: " + result);
+ return result;
+ }
+
+ private static void setBestPreviewFPS(Camera.Parameters parameters) {
+ // Required for Glass compatibility; also improves battery/CPU performance a tad
+ List supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange();
+ if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) {
+ int[] minimumSuitableFpsRange = null;
+ for (int[] fpsRange : supportedPreviewFpsRanges) {
+ int fpsMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+ if (fpsMax >= MIN_FPS * 1000 &&
+ (minimumSuitableFpsRange == null ||
+ fpsMax > minimumSuitableFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX])) {
+ minimumSuitableFpsRange = fpsRange;
+ }
+ }
+ if (minimumSuitableFpsRange == null) {
+ Log.i(TAG, "No suitable FPS range?");
+ } else {
+ int[] currentFpsRange = new int[2];
+ parameters.getPreviewFpsRange(currentFpsRange);
+ if (!Arrays.equals(currentFpsRange, minimumSuitableFpsRange)) {
+ Log.i(TAG, "Setting FPS range to " + Arrays.toString(minimumSuitableFpsRange));
+ parameters.setPreviewFpsRange(minimumSuitableFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ minimumSuitableFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ }
+ }
+ }
+
+}
diff --git a/glass/src/com/google/zxing/client/glass/CaptureActivity.java b/glass/src/com/google/zxing/client/glass/CaptureActivity.java
new file mode 100644
index 000000000..466f3537d
--- /dev/null
+++ b/glass/src/com/google/zxing/client/glass/CaptureActivity.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * 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.glass;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.Camera;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+import com.google.zxing.Result;
+import com.google.zxing.client.result.ParsedResult;
+import com.google.zxing.client.result.ParsedResultType;
+import com.google.zxing.client.result.ResultParser;
+import com.google.zxing.client.result.TextParsedResult;
+import com.google.zxing.client.result.URIParsedResult;
+
+import java.io.IOException;
+
+/**
+ * @author Sean Owen
+ */
+public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
+
+ private static final String TAG = CaptureActivity.class.getSimpleName();
+
+ private boolean hasSurface;
+ private SurfaceHolder holderWithCallback;
+ private Camera camera;
+ private DecodeRunnable decodeRunnable;
+ private Result result;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(R.layout.capture);
+ }
+
+ @Override
+ public synchronized void onResume() {
+ super.onResume();
+ SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
+ if (surfaceHolder == null) {
+ throw new IllegalStateException("No SurfaceHolder?");
+ }
+ if (hasSurface) {
+ initCamera(surfaceHolder);
+ } else {
+ surfaceHolder.addCallback(this);
+ holderWithCallback = surfaceHolder;
+ }
+ }
+
+ @Override
+ public synchronized void onPause() {
+ result = null;
+ if (decodeRunnable != null) {
+ decodeRunnable.stop();
+ decodeRunnable = null;
+ }
+ if (camera != null) {
+ camera.stopPreview();
+ camera.release();
+ camera = null;
+ }
+ if (holderWithCallback != null) {
+ holderWithCallback.removeCallback(this);
+ holderWithCallback = null;
+ }
+ super.onPause();
+ }
+
+ @Override
+ public synchronized void surfaceCreated(SurfaceHolder holder) {
+ Log.i(TAG, "Surface created");
+ holderWithCallback = null;
+ if (!hasSurface) {
+ hasSurface = true;
+ initCamera(holder);
+ }
+ }
+
+ @Override
+ public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ // do nothing
+ }
+
+ @Override
+ public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+ Log.i(TAG, "Surface destroyed");
+ holderWithCallback = null;
+ hasSurface = false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (result != null) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ handleResult(result);
+ return true;
+ case KeyEvent.KEYCODE_BACK:
+ reset();
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void initCamera(SurfaceHolder holder) {
+ if (camera != null) {
+ throw new IllegalStateException("Camera not null on initialization");
+ }
+ camera = Camera.open();
+ if (camera == null) {
+ throw new IllegalStateException("Camera is null");
+ }
+
+ CameraConfigurationManager.configure(camera);
+
+ try {
+ camera.setPreviewDisplay(holder);
+ camera.startPreview();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot start prevew", e);
+ }
+
+ decodeRunnable = new DecodeRunnable(this, camera);
+ new Thread(decodeRunnable).start();
+ reset();
+ }
+
+ void setResult(Result result) {
+ TextView statusView = (TextView) findViewById(R.id.status_view);
+ String text = result.getText();
+ statusView.setText(text);
+ statusView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.max(14, 72 - text.length() / 2));
+ statusView.setVisibility(View.VISIBLE);
+ this.result = result;
+ }
+
+ private void handleResult(Result result) {
+ ParsedResult parsed = ResultParser.parseResult(result);
+ Intent intent;
+ if (parsed.getType() == ParsedResultType.URI) {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(((URIParsedResult) parsed).getURI()));
+ } else {
+ intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.putExtra("query", ((TextParsedResult) parsed).getText());
+ }
+ startActivity(intent);
+ }
+
+ private void reset() {
+ TextView statusView = (TextView) findViewById(R.id.status_view);
+ statusView.setVisibility(View.GONE);
+ result = null;
+ decodeRunnable.startScanning();
+ }
+
+}
diff --git a/glass/src/com/google/zxing/client/glass/DecodeRunnable.java b/glass/src/com/google/zxing/client/glass/DecodeRunnable.java
new file mode 100644
index 000000000..4f94225ad
--- /dev/null
+++ b/glass/src/com/google/zxing/client/glass/DecodeRunnable.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * 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.glass;
+
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @author Sean Owen
+ */
+final class DecodeRunnable implements Runnable, Camera.PreviewCallback {
+
+ private static final String TAG = DecodeRunnable.class.getSimpleName();
+
+ private final CaptureActivity activity;
+ private final Camera camera;
+ private final int height;
+ private final int width;
+ private boolean running;
+ private Handler handler;
+ private final CountDownLatch handlerInitLatch;
+
+ DecodeRunnable(CaptureActivity activity, Camera camera) {
+ this.activity = activity;
+ this.camera = camera;
+ Camera.Parameters parameters = camera.getParameters();
+ Camera.Size previewSize = parameters.getPreviewSize();
+ height = previewSize.height;
+ width = previewSize.width;
+ running = true;
+ handlerInitLatch = new CountDownLatch(1);
+ }
+
+ private Handler getHandler() {
+ try {
+ handlerInitLatch.await();
+ } catch (InterruptedException ie) {
+ // continue?
+ }
+ return handler;
+ }
+
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ handler = new DecodeHandler();
+ handlerInitLatch.countDown();
+ Looper.loop();
+ }
+
+ void startScanning() {
+ getHandler().obtainMessage(R.id.decode_failed).sendToTarget();
+ }
+
+ void stop() {
+ getHandler().obtainMessage(R.id.quit).sendToTarget();
+ }
+
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ if (running) {
+ getHandler().obtainMessage(R.id.decode, data).sendToTarget();
+ }
+ }
+
+ private final class DecodeHandler extends Handler {
+
+ private final Map hints;
+
+ DecodeHandler() {
+ hints = new EnumMap<>(DecodeHintType.class);
+ hints.put(DecodeHintType.POSSIBLE_FORMATS,
+ Arrays.asList(BarcodeFormat.AZTEC, BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX));
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (!running) {
+ return;
+ }
+ switch (message.what) {
+ case R.id.decode:
+ decode((byte[]) message.obj);
+ break;
+ case R.id.decode_succeeded:
+ final Result result = (Result) message.obj;
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ activity.setResult(result);
+ }
+ });
+ break;
+ case R.id.decode_failed:
+ camera.setOneShotPreviewCallback(DecodeRunnable.this);
+ break;
+ case R.id.quit:
+ running = false;
+ Looper.myLooper().quit();
+ break;
+ }
+ }
+
+ private void decode(byte[] data) {
+ Result rawResult = null;
+ PlanarYUVLuminanceSource source =
+ new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false);
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ try {
+ rawResult = new MultiFormatReader().decode(bitmap, hints);
+ } catch (ReaderException re) {
+ // continue
+ }
+
+ Handler handler = getHandler();
+ Message message;
+ if (rawResult == null) {
+ message = handler.obtainMessage(R.id.decode_failed);
+ } else {
+ Log.i(TAG, "Decode succeeded: " + rawResult.getText());
+ message = handler.obtainMessage(R.id.decode_succeeded, rawResult);
+ }
+ message.sendToTarget();
+ }
+
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 956706303..6d8bbd065 100644
--- a/pom.xml
+++ b/pom.xml
@@ -509,6 +509,7 @@
android
androidtest
+ glass