diff --git a/CHANGES b/CHANGES index 7e4661a2b..9de2209e6 100644 --- a/CHANGES +++ b/CHANGES @@ -319,10 +319,11 @@ - Many ports of Java updates to C++ and C# ports - Standardize test system image processing and image format for reproducibility -2.3 (XX Dec 2013) +2.3.0 (1 Dec 2013) - Added clone of Google Chart Server QR code encoder API to zxingorg/, at endpoint /chart - Updated zxing.appspot.com generator to remove deprecated API usage, use latest GWT / App Engine + - Added skeleton Google Glass Mirror API app - Improve and standardize Maven build, standardize directories and version naming - Barcode Scanner Android up requires Android 4 and is updated to use Android 4+ APIs - javase/ and zxingorg/ modules now use Java 7 diff --git a/build.xml b/build.xml index 25cbefc08..5c0efc594 100644 --- a/build.xml +++ b/build.xml @@ -43,6 +43,7 @@ + diff --git a/glass-mirror/pom.xml b/glass-mirror/pom.xml new file mode 100755 index 000000000..7de963daa --- /dev/null +++ b/glass-mirror/pom.xml @@ -0,0 +1,145 @@ + + + + + 4.0.0 + + glass-mirror + 2.3.0-SNAPSHOT + war + + + + + com.google.apis + google-api-services-mirror + v1-rev28-1.17.0-rc + + + commons-logging + commons-logging + + + + + com.google.http-client + google-http-client-jackson2 + 1.17.0-rc + + + com.google.apis + google-api-services-oauth2 + v2-rev51-1.17.0-rc + + + + com.google.zxing + core + ${project.version} + + + com.google.zxing + javase + ${project.version} + + + + + com.fasterxml.jackson.core + jackson-core + 2.2.3 + + + commons-codec + commons-codec + 1.8 + + + org.apache.httpcomponents + httpclient + 4.3.1 + runtime + + + commons-logging + commons-logging + + + + + org.apache.httpcomponents + httpcore + 4.3 + + + + com.google.guava + guava + 15.0 + + + + org.slf4j + slf4j-api + 1.7.5 + + + org.slf4j + slf4j-jdk14 + 1.7.5 + runtime + + + org.slf4j + jcl-over-slf4j + 1.7.5 + runtime + + + javax + javaee-web-api + 6.0 + provided + + + + + + com.google.zxing + zxing-parent + 2.3.0-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + + + + + ZXing Google Glass Mirror API app + Experimental Barcode Scanner app for Google Glass Mirror API + + diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/AuthUtil.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/AuthUtil.java new file mode 100755 index 000000000..4693c2a05 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/AuthUtil.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 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. + */ + +/* + * Copyright 2013 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 com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.store.DataStoreFactory; +import com.google.api.client.util.store.MemoryDataStoreFactory; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +/** + * A collection of utility functions that simplify common authentication and + * user identity tasks + * + * @author Sean Owen + * @author Google + */ +final class AuthUtil { + + private static final Collection SCOPES = + Arrays.asList("https://www.googleapis.com/auth/glass.timeline", + "https://www.googleapis.com/auth/userinfo.profile"); + + private final DataStoreFactory dataStoreFactory; + private final String clientID; + private final String clientSecret; + + AuthUtil(String clientID, String clientSecret) { + dataStoreFactory = new MemoryDataStoreFactory(); + this.clientID = clientID; + this.clientSecret = clientSecret; + } + + /** + * Creates and returns a new {@link AuthorizationCodeFlow} for this app. + */ + public AuthorizationCodeFlow newAuthorizationCodeFlow() throws IOException { + return new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(), + new JacksonFactory(), + clientID, + clientSecret, + SCOPES) + .setAccessType("offline").setDataStoreFactory(dataStoreFactory).build(); + } + + public Credential getCredential(String userId) throws IOException { + return userId == null ? null : newAuthorizationCodeFlow().loadCredential(userId); + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/DecodeHelper.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/DecodeHelper.java new file mode 100755 index 000000000..dad9d24da --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/DecodeHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013 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 com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.Reader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.GlobalHistogramBinarizer; +import com.google.zxing.common.HybridBinarizer; + +import java.awt.color.CMMException; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; + +import javax.imageio.ImageIO; + +/** + * @author Sean Owen + */ +final class DecodeHelper { + + // No real reason to deal with more than maybe 8.3 megapixels + private static final int MAX_PIXELS = 1 << 23; + private static final Map HINTS; + + static { + HINTS = new EnumMap<>(DecodeHintType.class); + HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); + HINTS.put(DecodeHintType.POSSIBLE_FORMATS, EnumSet.allOf(BarcodeFormat.class)); + } + + private DecodeHelper() { + } + + static Collection processStream(InputStream is) throws IOException { + BufferedImage image; + try { + image = ImageIO.read(is); + } catch (CMMException | IllegalArgumentException e) { + throw new IOException(e); + } + if (image == null) { + throw new IOException("No image"); + } + if (image.getHeight() <= 1 || image.getWidth() <= 1 || + image.getHeight() * image.getWidth() > MAX_PIXELS) { + throw new IOException("Dimensions out of bounds: " + image.getWidth() + 'x' + image.getHeight()); + } + + return processImage(image); + } + + private static Collection processImage(BufferedImage image) { + + Reader reader = new MultiFormatReader(); + LuminanceSource source = new BufferedImageLuminanceSource(image); + BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source)); + Collection results = new ArrayList<>(1); + + if (results.isEmpty()) { + try { + // Look for normal barcode in photo + Result theResult = reader.decode(bitmap, HINTS); + if (theResult != null) { + results.add(theResult); + } + } catch (ReaderException re) { + // continue + } + } + + if (results.isEmpty()) { + try { + // Try again with other binarizer + BinaryBitmap hybridBitmap = new BinaryBitmap(new HybridBinarizer(source)); + Result theResult = reader.decode(hybridBitmap, HINTS); + if (theResult != null) { + results.add(theResult); + } + } catch (ReaderException re) { + // continue + } + } + + return results; + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/NewUserSetupServlet.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/NewUserSetupServlet.java new file mode 100755 index 000000000..558d84425 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/NewUserSetupServlet.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2013 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. + */ + +/* + * Copyright 2013 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 java.io.IOException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.ResourceBundle; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.auth.oauth2.TokenResponse; +import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse; +import com.google.api.client.http.GenericUrl; +import com.google.api.services.mirror.model.Contact; +import com.google.api.services.mirror.model.Subscription; +import com.google.api.services.mirror.model.TimelineItem; +import com.google.zxing.client.glass.mirror.MirrorClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This servlet manages the OAuth 2.0 dance. + * + * @author Jenny Murphy - http://google.com/+JennyMurphy + * @author Sean Owen + * @link https://developers.google.com/glass/develop/mirror/authorization + */ +public final class NewUserSetupServlet extends HttpServlet { + + private static final Logger log = LoggerFactory.getLogger(NewUserSetupServlet.class); + + private static final String BS_ID = "barcode-scanner"; + + private AuthUtil authUtil; + private String baseURL; + private String notificationCallbackURL; + private String subscriptionVerifyToken; + + @Override + public void init(ServletConfig servletConfig) { + ServletContext context = servletConfig.getServletContext(); + authUtil = new AuthUtil(context.getInitParameter("CLIENT_ID"), context.getInitParameter("CLIENT_SECRET")); + baseURL = context.getInitParameter("BASE_URL"); + notificationCallbackURL = baseURL + "/notification"; + subscriptionVerifyToken = context.getInitParameter("SUBSCRIPTION_VERIFY_TOKEN"); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + + String redirect; + + if (request.getParameter("error") != null) { + + log.warn("OAuth error: {}", Arrays.toString(request.getParameterValues("error"))); + redirect = baseURL; + + } else { + + GenericUrl url1 = new GenericUrl(request.getRequestURL().toString()); + url1.setRawPath("/oauth2callback"); + String callbackURL = url1.build(); + String code = request.getParameter("code"); + + if (code == null) { + + log.info("New OAuth flow"); + + AuthorizationCodeFlow flow = authUtil.newAuthorizationCodeFlow(); + GenericUrl url = flow.newAuthorizationUrl().setRedirectUri(callbackURL); + url.set("approval_prompt", "force"); + redirect = url.build(); + + } else { + // If we have a code, finish the OAuth 2.0 dance + + log.info("OAuth response, code: {}", code); + + AuthorizationCodeFlow flow = authUtil.newAuthorizationCodeFlow(); + TokenResponse tokenResponse = flow.newTokenRequest(code).setRedirectUri(callbackURL).execute(); + + // Extract the Google User ID from the ID token in the auth response + String userId = ((GoogleTokenResponse) tokenResponse).parseIdToken().getPayload().getSubject(); + + // Set it into the session + //AuthUtil.setUserId(request, userId); + flow.createAndStoreCredential(tokenResponse, userId); + + ResourceBundle resources = ResourceBundle.getBundle("Strings", request.getLocale()); + + bootstrapNewUser(userId, resources); + + redirect = "glass/index.jspx"; + } + } + + response.sendRedirect(redirect); + } + + private void bootstrapNewUser(String userId, ResourceBundle resources) throws IOException { + Credential credential = authUtil.newAuthorizationCodeFlow().loadCredential(userId); + + String appName = resources.getString("app.title"); + + MirrorClient mirrorClient = MirrorClient.get(); + + boolean contactExists = false; + for (Contact contact : mirrorClient.listContacts(credential)) { + if (BS_ID.equals(contact.getId())) { + contactExists = true; + break; + } + } + + if (!contactExists) { + Contact contact = new Contact(); + contact.setId(BS_ID); + contact.setDisplayName(appName); + contact.setAcceptTypes(Arrays.asList("image/png", "image/jpeg", "image/gif", "image/bmp")); + mirrorClient.insertContact(credential, contact); + } + + boolean subscriptionExists = false; + for (Subscription subscription : mirrorClient.listSubscriptions(credential)) { + if (notificationCallbackURL.equals(subscription.getCallbackUrl())) { + subscriptionExists = true; + break; + } + } + + if (!subscriptionExists) { + Subscription subscription = new Subscription(); + subscription.setCollection("timeline"); + subscription.setOperation(Collections.singletonList("INSERT")); + subscription.setCallbackUrl(notificationCallbackURL); + subscription.setVerifyToken(subscriptionVerifyToken); + subscription.setUserToken(userId); + mirrorClient.insertSubscription(credential, subscription); + } + + TimelineItem timelineItem = new TimelineItem(); + timelineItem.setTitle(MessageFormat.format(resources.getString("setup.welcome.title"), appName)); + timelineItem.setText(MessageFormat.format(resources.getString("setup.welcome.text"), appName)); + mirrorClient.insertTimelineItem(credential, timelineItem); + + } +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationCallable.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationCallable.java new file mode 100755 index 000000000..ead95e079 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationCallable.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013 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 java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.List; +import java.util.ResourceBundle; +import java.util.concurrent.Callable; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.services.mirror.model.Attachment; +import com.google.api.services.mirror.model.TimelineItem; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedResult; +import com.google.zxing.client.result.ResultParser; +import com.google.zxing.client.result.URIParsedResult; +import com.google.zxing.client.glass.mirror.MirrorClient; + +/** + * @author Sean Owen + */ +final class NotificationCallable implements Callable { + + private final Credential credential; + private final String timelineItemID; + private final ResourceBundle resources; + + NotificationCallable(Credential credential, String timelineItemID, ResourceBundle resources) { + this.credential = credential; + this.timelineItemID = timelineItemID; + this.resources = resources; + } + + @Override + public Void call() throws Exception { + MirrorClient mirrorClient = MirrorClient.get(); + TimelineItem timelineItem = mirrorClient.getTimelineItem(credential, timelineItemID); + if (timelineItem == null) { + return null; + } + List attachments = timelineItem.getAttachments(); + if (attachments == null || attachments.isEmpty()) { + return null; + } + Attachment attachment = attachments.get(0); + String attachmentId = attachment.getId(); + + String errorMessage = null; + Collection results = null; + try (InputStream in = mirrorClient.getAttachmentInputStream(credential, timelineItemID, attachmentId)) { + results = DecodeHelper.processStream(in); + } catch (IOException | RuntimeException e) { + errorMessage = e.toString(); + } + + if (results == null || results.isEmpty()) { + + TimelineItem resultTimelineItem = new TimelineItem(); + if (errorMessage == null) { + resultTimelineItem.setTitle(resources.getString("scan.result.notfound.title")); + resultTimelineItem.setText(resources.getString("scan.result.notfound.text")); + } else { + resultTimelineItem.setTitle(resources.getString("scan.result.error.title")); + resultTimelineItem.setText(MessageFormat.format(resources.getString("scan.result.error.text"), errorMessage)); + } + mirrorClient.insertTimelineItem(credential, resultTimelineItem); + + } else { + + for (Result result : results) { + ParsedResult parsedResult = ResultParser.parseResult(result); + + TimelineItem resultTimelineItem = new TimelineItem(); + resultTimelineItem.setText(parsedResult.getDisplayResult()); + resultTimelineItem.setTitle( + MessageFormat.format(resources.getString("scan.result.success.title"), parsedResult.getType())); + + addResultDetailToTimelineItem(parsedResult, resultTimelineItem); + + mirrorClient.insertTimelineItem(credential, resultTimelineItem); + } + + } + + return null; + } + + private static void addResultDetailToTimelineItem(ParsedResult parsedResult, TimelineItem item) { + if (parsedResult instanceof URIParsedResult) { + addURIResultDetailToTimelineItem((URIParsedResult) parsedResult, item); + } + } + + private static void addURIResultDetailToTimelineItem(URIParsedResult parsedResult, TimelineItem item) { + String uri = parsedResult.getURI(); + item.setHtml("" + parsedResult.getDisplayResult() + ""); + item.setCanonicalUrl(uri); + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationServlet.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationServlet.java new file mode 100755 index 000000000..5fb200ea7 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/NotificationServlet.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 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 java.io.IOException; +import java.util.ResourceBundle; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.mirror.model.Notification; + +/** + * @author Sean Owen + * @link https://developers.google.com/glass/develop/mirror/timeline + */ +public final class NotificationServlet extends HttpServlet { + + private ExecutorService executor; + private final JsonObjectParser jsonParser = new JsonObjectParser(new JacksonFactory()); + private AuthUtil authUtil; + private String subscriptionVerifyToken; + + @Override + public void init(ServletConfig servletConfig) { + ServletContext context = servletConfig.getServletContext(); + authUtil = new AuthUtil(context.getInitParameter("CLIENT_ID"), context.getInitParameter("CLIENT_SECRET")); + executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + subscriptionVerifyToken = context.getInitParameter("SUBSCRIPTION_VERIFY_TOKEN"); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + + Notification notification = jsonParser.parseAndClose(request.getReader(), Notification.class); + + if (!subscriptionVerifyToken.equals(notification.getVerifyToken())) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + String userId = notification.getUserToken(); + Credential credential = authUtil.newAuthorizationCodeFlow().loadCredential(userId); + String timelineItemID = notification.getItemId(); + + ResourceBundle resources = ResourceBundle.getBundle("Strings", request.getLocale()); + + executor.submit(new NotificationCallable(credential, timelineItemID, resources)); + } + + @Override + public void destroy() { + executor.shutdownNow(); + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/GoogleAPIMirrorClient.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/GoogleAPIMirrorClient.java new file mode 100755 index 000000000..f12b6a582 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/GoogleAPIMirrorClient.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 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. + */ + +/* + * Copyright 2013 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.mirror; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.mirror.Mirror; +import com.google.api.services.mirror.model.Attachment; +import com.google.api.services.mirror.model.Contact; +import com.google.api.services.mirror.model.Subscription; +import com.google.api.services.mirror.model.TimelineItem; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +final class GoogleAPIMirrorClient extends MirrorClient { + + @Override + public void insertContact(Credential credential, Contact contact) throws IOException { + getMirror(credential).contacts().insert(contact).execute(); + } + + @Override + public Collection listContacts(Credential credential) throws IOException { + return getMirror(credential).contacts().list().execute().getItems(); + } + + @Override + public void insertTimelineItem(Credential credential, TimelineItem item) throws IOException { + getMirror(credential).timeline().insert(item).execute(); + } + + @Override + public void insertSubscription(Credential credential, Subscription subscription) throws IOException { + getMirror(credential).subscriptions().insert(subscription).execute(); + } + + @Override + public Collection listSubscriptions(Credential credential) throws IOException { + return getMirror(credential).subscriptions().list().execute().getItems(); + } + + @Override + public TimelineItem getTimelineItem(Credential credential, String id) throws IOException { + return getMirror(credential).timeline().get(id).execute(); + } + + @Override + public InputStream getAttachmentInputStream(Credential credential, String timelineItemId, String attachmentId) + throws IOException { + Mirror mirrorService = getMirror(credential); + Mirror.Timeline.Attachments attachments = mirrorService.timeline().attachments(); + Attachment attachmentMetadata = attachments.get(timelineItemId, attachmentId).execute(); + HttpResponse resp = + mirrorService.getRequestFactory() + .buildGetRequest(new GenericUrl(attachmentMetadata.getContentUrl())).execute(); + return resp.getContent(); + } + + private static Mirror getMirror(Credential credential) { + return new Mirror.Builder(new NetHttpTransport(), new JacksonFactory(), credential).build(); + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MemoryMirrorClient.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MemoryMirrorClient.java new file mode 100755 index 000000000..7ae382257 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MemoryMirrorClient.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 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.mirror; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.services.mirror.model.Attachment; +import com.google.api.services.mirror.model.Contact; +import com.google.api.services.mirror.model.Subscription; +import com.google.api.services.mirror.model.TimelineItem; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class MemoryMirrorClient extends MirrorClient { + + private static final Logger log = LoggerFactory.getLogger(MemoryMirrorClient.class); + + private static final String FAKE_ATTACHMENT_ID = "0123"; + + private final Collection contacts = + Collections.synchronizedCollection(Sets.newHashSet()); + private final Collection subscriptions = + Collections.synchronizedCollection(Sets.newHashSet()); + + @Override + public void insertContact(Credential credential, Contact contact) { + log.info("Insert Contact: {}", contact); + contacts.add(contact); + } + + @Override + public Collection listContacts(Credential credential) { + log.info("Contacts: {}", contacts); + return contacts; + } + + @Override + public void insertTimelineItem(Credential credential, TimelineItem item) { + log.info("Insert Timeline: {}", item); + } + + @Override + public void insertSubscription(Credential credential, Subscription subscription) { + log.info("Insert Subscription: {}", subscription); + subscriptions.add(subscription); + } + + @Override + public Collection listSubscriptions(Credential credential) { + log.info("Subscriptions: {}", subscriptions); + return subscriptions; + } + + @Override + public TimelineItem getTimelineItem(Credential credential, String id) { + log.info("Get Timeline {}", id); + TimelineItem timelineItem = new TimelineItem(); + timelineItem.setId(id); + Attachment fakeAttachment = new Attachment(); + fakeAttachment.setId(FAKE_ATTACHMENT_ID); + timelineItem.setAttachments(Collections.singletonList(fakeAttachment)); + return timelineItem; + } + + @Override + public InputStream getAttachmentInputStream(Credential credential, String timelineItemId, String attachmentId) { + log.info("Get Attachment {} {}", timelineItemId, attachmentId); + if (!FAKE_ATTACHMENT_ID.equals(attachmentId)) { + return null; + } + return MemoryMirrorClient.class.getResourceAsStream("/test.jpg"); + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MirrorClient.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MirrorClient.java new file mode 100755 index 000000000..746b51c25 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/mirror/MirrorClient.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 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.mirror; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.services.mirror.model.Contact; +import com.google.api.services.mirror.model.Subscription; +import com.google.api.services.mirror.model.TimelineItem; + +public abstract class MirrorClient { + + private static MirrorClient instance = null; + + public static synchronized MirrorClient get() { + if (instance == null) { + instance = new GoogleAPIMirrorClient(); + } + return instance; + } + + public abstract void insertContact(Credential credential, Contact contact) throws IOException; + + public abstract Collection listContacts(Credential credential) throws IOException; + + public abstract void insertTimelineItem(Credential credential, TimelineItem item) throws IOException; + + public abstract void insertSubscription(Credential credential, Subscription subscription) throws IOException; + + public abstract Collection listSubscriptions(Credential credential) throws IOException; + + public abstract TimelineItem getTimelineItem(Credential credential, String id) throws IOException; + + public abstract InputStream getAttachmentInputStream(Credential credential, + String timelineItemId, + String attachmentId) throws IOException; + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStore.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStore.java new file mode 100644 index 000000000..b767734b7 --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStore.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013 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.store; + +import com.google.api.client.auth.oauth2.StoredCredential; +import com.google.api.client.util.store.AbstractDataStore; +import com.google.api.client.util.store.DataStore; +import com.google.api.client.util.store.DataStoreFactory; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +final class MySQLDataStore extends AbstractDataStore { + + MySQLDataStore(DataStoreFactory dataStoreFactory, String id) { + super(dataStoreFactory, id); + } + + @Override + public Set keySet() { + return Collections.singleton(StoredCredential.DEFAULT_DATA_STORE_ID); + } + + @Override + public Collection values() throws IOException { + return null; // TODO + } + + @Override + public V get(String key) throws IOException { + return null; // TODO + } + + @Override + public DataStore set(String key, V value) throws IOException { + return null; // TODO + } + + @Override + public DataStore clear() throws IOException { + return null; // TODO + } + + @Override + public DataStore delete(String key) throws IOException { + return null; // TODO + } + +} diff --git a/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStoreFactory.java b/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStoreFactory.java new file mode 100755 index 000000000..371a5d05b --- /dev/null +++ b/glass-mirror/src/main/java/com/google/zxing/client/glass/store/MySQLDataStoreFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013 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.store; + +import com.google.api.client.util.store.AbstractDataStoreFactory; +import com.google.api.client.util.store.DataStore; + +import java.io.Serializable; + +final class MySQLDataStoreFactory extends AbstractDataStoreFactory { + + @Override + protected DataStore createDataStore(String id) { + return new MySQLDataStore<>(this, id); + } + +} diff --git a/glass-mirror/src/main/resources/Strings.properties b/glass-mirror/src/main/resources/Strings.properties new file mode 100755 index 000000000..0c2c86193 --- /dev/null +++ b/glass-mirror/src/main/resources/Strings.properties @@ -0,0 +1,8 @@ +app.title=Barcode Scanner for Google Glass +setup.welcome.title=Welcome to {0} +setup.welcome.text=To read a barcode, share a photo of it with the contact called '{0}' +scan.result.notfound.title=No Barcode Found +scan.result.notfound.text=Sorry, no barcode could be found it that image. Try sharing another image. +scan.result.error.title=Error +scan.result.error.text=An error occurred while looking for a barcode: {0} +scan.result.success.title={0} Barcode \ No newline at end of file diff --git a/glass-mirror/src/main/resources/test.jpg b/glass-mirror/src/main/resources/test.jpg new file mode 100755 index 000000000..ca9ceef7f Binary files /dev/null and b/glass-mirror/src/main/resources/test.jpg differ diff --git a/glass-mirror/src/main/webapp/WEB-INF/web.xml b/glass-mirror/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 000000000..87e1120cb --- /dev/null +++ b/glass-mirror/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,104 @@ + + + + + + + + + + CLIENT_ID + TODO + + + CLIENT_SECRET + TODO + + + + BASE_URL + http://example.org/todo + + + + SUBSCRIPTION_VERIFY_TOKEN + TODO + + + + OAuth2CallbackServlet + com.google.zxing.client.glass.NewUserSetupServlet + + + NotificationServlet + com.google.zxing.client.glass.NotificationServlet + + + + OAuth2CallbackServlet + /oauth2callback + /start + + + NotificationServlet + /notification + + + + java.lang.Throwable + /error.jspx + + + 400 + /error.jspx + + + 401 + /error.jspx + + + 404 + /error.jspx + + + 405 + /error.jspx + + + 500 + /error.jspx + + + 501 + /error.jspx + + + 503 + /error.jspx + + + diff --git a/glass-mirror/src/main/webapp/error.jspx b/glass-mirror/src/main/webapp/error.jspx new file mode 100755 index 000000000..cb8cfb872 --- /dev/null +++ b/glass-mirror/src/main/webapp/error.jspx @@ -0,0 +1,41 @@ + + + + + + + + +]]> + + +Error + + + +

Error ${pageContext.errorData.statusCode} : ${pageContext.errorData.requestURI}

+

+
+Throwable t = pageContext.getErrorData().getThrowable();
+if (t != null) {
+  t.printStackTrace(new PrintWriter(out));
+}
+
+

+ + +
\ No newline at end of file diff --git a/glass-mirror/src/main/webapp/glass/index.jspx b/glass-mirror/src/main/webapp/glass/index.jspx new file mode 100755 index 000000000..d15710ee4 --- /dev/null +++ b/glass-mirror/src/main/webapp/glass/index.jspx @@ -0,0 +1,31 @@ + + + + + +]]> + + + + Barcode Scanner for Google Glass + + + +

Success

+ + +
diff --git a/glass-mirror/src/main/webapp/glass/style.css b/glass-mirror/src/main/webapp/glass/style.css new file mode 100755 index 000000000..6eae79835 --- /dev/null +++ b/glass-mirror/src/main/webapp/glass/style.css @@ -0,0 +1,3 @@ +body {background-color:black;margin:0;padding:0} +body,th,td,p {font-family:Helvetica,Arial,sans-serif;color:white} +td {vertical-align:top} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 137078ee0..70629cc63 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ zxingorg android androidtest + glass-mirror