Initial commit of Google Glass GDK app

This commit is contained in:
Sean Owen 2014-04-26 12:02:51 +01:00
parent 98bc57f5d1
commit 02e0512a62
14 changed files with 761 additions and 0 deletions

6
glass/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
target/
*.iml
bin/
gen/
libs/
local.properties

43
glass/AndroidManifest.xml Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.glass"
android:versionCode="2"
android:versionName="0.2.0">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19"/>
<uses-feature android:name="android.hardware.camera" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:allowBackup="true">
<activity android:name=".CaptureActivity">
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/barcode_scanner_show" />
</activity>
</application>
</manifest>

55
glass/pom.xml Normal file
View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>glass</artifactId>
<version>0.2.0</version>
<packaging>apk</packaging>
<dependencies>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
</dependency>
</dependencies>
<parent>
<groupId>com.google.zxing</groupId>
<artifactId>zxing-parent</artifactId>
<version>3.0.2-SNAPSHOT</version>
</parent>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<name>Android Barcode Scanner app for Google Glass</name>
<description>Provides the Google Glass app "Barcode Scanner"</description>
</project>

68
glass/proguard.cfg Normal file
View file

@ -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 <methods>;
}
# 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 <fields>;
}
# 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.**

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<SurfaceView android:id="@+id/preview_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<TextView android:id="@+id/status_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/result_view"
android:textSize="54sp"
android:padding="10dip"
android:textColor="@color/result_text"
android:visibility="gone"/>
</merge>

20
glass/res/values/colors.xml Executable file
View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<resources>
<color name="result_text">#ffffffff</color>
<color name="result_view">#b0000000</color>
</resources>

22
glass/res/values/ids.xml Executable file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<resources>
<item type="id" name="decode"/>
<item type="id" name="decode_failed"/>
<item type="id" name="decode_succeeded"/>
<item type="id" name="quit"/>
</resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<resources>
<string name="app_name">Barcode Scanner for Glass</string>
</resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<trigger command="TRANSLATE_THIS">
<constraints camera="true" />
</trigger>

View file

@ -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<Camera.Area> 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<String> 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<int[]> 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]);
}
}
}
}
}

View file

@ -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();
}
}

View file

@ -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<DecodeHintType,Object> 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();
}
}
}

View file

@ -509,6 +509,7 @@
<modules> <modules>
<module>android</module> <module>android</module>
<module>androidtest</module> <module>androidtest</module>
<module>glass</module>
</modules> </modules>
</profile> </profile>
<profile> <profile>