diff --git a/README b/README
deleted file mode 100644
index 8d4bd1c..0000000
--- a/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Play for Work
-=============
diff --git a/examples/emm-notifications/.gitignore b/examples/emm-notifications/.gitignore
new file mode 100644
index 0000000..0743fe9
--- /dev/null
+++ b/examples/emm-notifications/.gitignore
@@ -0,0 +1,6 @@
+.idea
+target
+.DS_Store
+../.DS_Store
+emm-notifications.iml
+.idea
diff --git a/examples/emm-notifications/README.md b/examples/emm-notifications/README.md
new file mode 100644
index 0000000..ce120ce
--- /dev/null
+++ b/examples/emm-notifications/README.md
@@ -0,0 +1,4 @@
+Play for Work EMM Push Notification
+==================================
+
+Text goes here.
diff --git a/examples/emm-notifications/google_settings.properties b/examples/emm-notifications/google_settings.properties
new file mode 100644
index 0000000..36c9205
--- /dev/null
+++ b/examples/emm-notifications/google_settings.properties
@@ -0,0 +1,12 @@
+# NOT PUBLIC
+# Values for our internal developer console project (enterprise-cloud-pub-sub)
+ServiceAccountEmail=368628613713-t4hf388f34tdn5lhpdcu1qqfgio01626@developer.gserviceaccount.com
+
+# Download from here: https://pantheon.corp.google.com/project/enterprise-cloud-pub-sub/apiui/credential
+ServiceAccountP12KeyFile=secret.p12
+ProjectName=enterprise-cloud-pub-sub
+SubscriptionName=projects/enterprise-cloud-pub-sub/subscriptions/push-subscription
+TopicName=projects/enterprise-cloud-pub-sub/topics/default-topic
+
+# Configured for the project mentioned above
+PushEndpoint=e.r-k.co
diff --git a/examples/emm-notifications/pom.xml b/examples/emm-notifications/pom.xml
new file mode 100644
index 0000000..a32affa
--- /dev/null
+++ b/examples/emm-notifications/pom.xml
@@ -0,0 +1,110 @@
+
+ 4.0.0
+ com.google.android.work.emmnotifications
+ emm-notifications
+ jar
+ 1.0-SNAPSHOT
+ emm-notifications
+ http://maven.apache.org
+
+
+
+ protoc-plugin
+ http://sergei-ivanov.github.com/maven-protoc-plugin/repo/releases/
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.5
+ 1.5
+
+
+
+ com.google.protobuf.tools
+ maven-protoc-plugin
+ 0.3.2
+
+ /usr/bin/protoc
+
+
+
+
+ compile
+ testCompile
+
+
+
+
+
+ maven-assembly-plugin
+
+
+
+ fully.qualified.MainClass
+
+
+
+ jar-with-dependencies
+
+
+
+
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ com.google.protobuf
+ protobuf-java
+ 2.6.1
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ com.google.apis
+ google-api-services-pubsub
+ LATEST
+
+
+ com.google.api-client
+ google-api-client
+ 1.18.0-rc
+
+
+ com.google.oauth-client
+ google-oauth-client-java6
+ 1.15.0-rc
+
+
+ com.google.http-client
+ google-http-client-jackson2
+ 1.19.0
+
+
+ com.google.guava
+ guava
+ 18.0
+
+
+ commons-cli
+ commons-cli
+ 1.2
+
+
+
diff --git a/examples/emm-notifications/settings.properties b/examples/emm-notifications/settings.properties
new file mode 100644
index 0000000..e165ba7
--- /dev/null
+++ b/examples/emm-notifications/settings.properties
@@ -0,0 +1,11 @@
+# Create new service account at https://console.developers.google.com
+ServiceAccountEmail=changeme@gserviceaccount.com
+ServiceAccountP12KeyFile=/path/to/key.p12
+
+# This will be the name of the service account you will create
+ProjectName=sample-project-name
+SubscriptionName=projects/sample-project-name/subscriptions/default
+TopicName=projects/sample-project-name/topics/default
+
+# Define new push endpoint in developer console project
+PushEndpoint=sample.push.endpoint
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java
new file mode 100644
index 0000000..ac42447
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java
@@ -0,0 +1,89 @@
+package com.google.android.work.emmnotifications;
+
+import com.google.android.work.pubsub.EmmPubsub;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.api.services.pubsub.Pubsub;
+import com.google.api.services.pubsub.model.PublishRequest;
+import com.google.api.services.pubsub.model.PubsubMessage;
+import com.google.api.services.pubsub.model.Topic;
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.logging.Logger;
+
+/**
+ * This is a faux publisher which can be used to simulate messages that would be normally sent by
+ * Play for Work API
+ *
+ * To run this sample code:
+ *
+ * - Modify settings.properties or specify a different file via DEVELOPER_CONSOLE_SETTINGS
+ * environment variable
+ * - Build a deploy jar using
mvn clean compile assembly:single
+ * - Execute it as
+ * java -cp target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar \
+ * com.google.android.work.emmnotifications.FauxPublisher
+ *
+
+ */
+public class FauxPublisher {
+
+ private static final Logger LOG =
+ Logger.getLogger(FauxPublisher.class.getName());
+
+ public static void main(String[] args)
+ throws IOException, GeneralSecurityException {
+
+ Pubsub pubsubClient = ServiceAccountConfiguration.createPubsubClient(
+ Settings.getSettings().getServiceAccountEmail(),
+ Settings.getSettings().getServiceAccountP12KeyPath());
+ String topicName = Settings.getSettings().getTopicName();
+
+ try {
+ Topic topic = pubsubClient
+ .projects()
+ .topics()
+ .get(topicName)
+ .execute();
+
+ LOG.info("Topic " + topicName + " exists: " + topic.toPrettyString());
+ } catch (HttpResponseException e) {
+ if (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
+ // Topic doesn't exist?
+ LOG.info("Topic " + topicName + " doesn't exists, creating it");
+ pubsubClient
+ .projects()
+ .topics()
+ .create(topicName, new Topic())
+ .execute();
+ LOG.info("Topic " + topicName + " created");
+ }
+ }
+
+ ImmutableList.Builder listBuilder = ImmutableList.builder();
+
+ EmmPubsub.MdmPushNotification mdmPushNotification = EmmPubsub.MdmPushNotification.newBuilder()
+ .addProductApprovalEvent(EmmPubsub.ProductApprovalEvent.newBuilder()
+ .setApproved(false)
+ .setProductId("com.google.android.gms")
+ .setCommonEventInformation(EmmPubsub.MdmNotificationEnterpriseEventCommon.newBuilder()
+ .setEnterpriseId("12321321")
+ .setEventNotificationSentTimestamp("right now")))
+ .build();
+
+ PublishRequest publishRequest = new PublishRequest()
+ .setMessages(ImmutableList.of(new PubsubMessage()
+ .encodeData(mdmPushNotification.toByteArray())));
+
+ LOG.info("Publishing a request: " + publishRequest.toPrettyString());
+
+ pubsubClient
+ .projects()
+ .topics()
+ .publish(topicName, publishRequest)
+ .execute();
+ }
+
+}
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java
new file mode 100644
index 0000000..f0d9087
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java
@@ -0,0 +1,128 @@
+package com.google.android.work.emmnotifications;
+
+import com.google.android.work.pubsub.EmmPubsub;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.api.client.json.JsonParser;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.pubsub.Pubsub;
+import com.google.api.services.pubsub.model.PubsubMessage;
+import com.google.api.services.pubsub.model.PushConfig;
+import com.google.api.services.pubsub.model.Subscription;
+import com.google.common.io.CharStreams;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This is a sample subscriber code. To run it you need to have an SSL endpoint configured
+ * and run this code either on port 443 (update below) on the server backing this endpoint or
+ * configure a reverse proxy from port 443 to 8093.
+ *
+ * Details: [link will be here]
+ *
+ * To run this sample code:
+ *
+ * - Modify settings.properties or specify a different file via DEVELOPER_CONSOLE_SETTINGS
+ * environment variable
+ * - Build a deploy jar using
mvn clean compile assembly:single
+ * - Execute it as
+ * java -cp target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar \
+ * com.google.android.work.emmnotifications.PushSubscriber
+ *
+ */
+public class PushSubscriber {
+ // this can be any port
+ private static final int PORT = 8093;
+
+ private static final Logger LOG = Logger.getLogger(PushSubscriber.class.getName());
+ public static final String MESSAGE_FIELD = "message";
+
+ public static void main(String[] args) throws Exception {
+ Pubsub client = ServiceAccountConfiguration.createPubsubClient(
+ Settings.getSettings().getServiceAccountEmail(),
+ Settings.getSettings().getServiceAccountP12KeyPath());
+
+ // First we check if subscription actually exists for this subscription name.
+ Subscription subscription = null;
+
+ String topicName = Settings.getSettings().getTopicName();
+ String subName = Settings.getSettings().getSubscriptionName();
+
+ LOG.info("Will be using topic name: " + topicName + ", subscription name: " + subName);
+
+ try {
+ LOG.info("Trying to get subscription named " + subName);
+ subscription = client
+ .projects()
+ .subscriptions()
+ .get(subName)
+ .execute();
+
+ LOG.info("Will be re-using existing subscription: " + subscription.toPrettyString());
+ } catch (HttpResponseException e) {
+
+ // subscription not found
+ if (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
+ LOG.info("Subscription doesn't exist, will try to create " + subName);
+
+ // Creating subscription
+ subscription = client
+ .projects()
+ .subscriptions()
+ .create(subName, new Subscription()
+ .setTopic(topicName) // Name of the topic it subscribes to
+ .setAckDeadlineSeconds(600)
+ .setPushConfig(new PushConfig()
+ // FQDN with valid SSL certificate
+ .setPushEndpoint(Settings.getSettings().getPushEndpoint())))
+ .execute();
+
+ LOG.info("Created: " + subscription.toPrettyString());
+ }
+ }
+
+ // Kicking off HttpServer which will listen on specified port and process all
+ // incoming push pub/sub notifications
+ HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
+ server.createContext("/", new HttpHandler() {
+ public void handle(HttpExchange httpExchange) throws IOException {
+ String rawRequest = CharStreams.toString(
+ new InputStreamReader(httpExchange.getRequestBody()));
+ LOG.info("Raw request: " + rawRequest);
+
+ try {
+ // Note, that documentation says this is PubsubMessage, which it isn't
+ JsonParser parser = JacksonFactory.getDefaultInstance().createJsonParser(rawRequest);
+ parser.skipToKey(MESSAGE_FIELD);
+
+ PubsubMessage message = parser.parseAndClose(PubsubMessage.class);
+ LOG.info("Pubsub message received: " + message.toPrettyString());
+
+ // Decoding Protocol Buffers message from array of bytes
+ EmmPubsub.MdmPushNotification mdmPushNotification = EmmPubsub.MdmPushNotification
+ .newBuilder()
+ .mergeFrom(message.decodeData())
+ .build();
+
+ LOG.info("Message received: " + mdmPushNotification.toString());
+ } catch (Throwable e) {
+ LOG.log(Level.WARNING, "Error occured when decoding message", e);
+ }
+
+ // CloudPubSub will interpret 2XX as ACK, anything that isn't 2XX will trigger a retry
+ httpExchange.sendResponseHeaders(204, 0);
+ httpExchange.close();
+ }
+ });
+
+ server.setExecutor(null);
+ server.start(); // Will keep running until killed
+ }
+}
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java
new file mode 100644
index 0000000..56b1488
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java
@@ -0,0 +1,96 @@
+package com.google.android.work.emmnotifications;
+
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.http.HttpBackOffIOExceptionHandler;
+import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpRequestInitializer;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
+import com.google.api.client.util.ExponentialBackOff;
+import com.google.api.client.util.Sleeper;
+import com.google.common.base.Preconditions;
+import com.google.common.io.CharStreams;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.logging.Logger;
+
+/**
+ * RetryHttpInitializerWrapper will automatically retry upon RPC
+ * failures, preserving the auto-refresh behavior of the Google
+ * Credentials.
+ */
+public class RetryHttpInitializerWrapper implements HttpRequestInitializer {
+
+ private static final Logger LOG =
+ Logger.getLogger(RetryHttpInitializerWrapper.class.getName());
+
+ // Intercepts the request for filling in the "Authorization"
+ // header field, as well as recovering from certain unsuccessful
+ // error codes wherein the Credential must refresh its token for a
+ // retry.
+ private final Credential wrappedCredential;
+
+ // A sleeper; you can replace it with a mock in your test.
+ private final Sleeper sleeper;
+
+ public RetryHttpInitializerWrapper(Credential wrappedCredential) {
+ this(wrappedCredential, Sleeper.DEFAULT);
+ }
+
+ // Use only for testing.
+ RetryHttpInitializerWrapper(
+ Credential wrappedCredential, Sleeper sleeper) {
+ this.wrappedCredential = Preconditions.checkNotNull(wrappedCredential);
+ this.sleeper = sleeper;
+ }
+
+ public void initialize(HttpRequest request) {
+ final HttpUnsuccessfulResponseHandler backoffHandler =
+ new HttpBackOffUnsuccessfulResponseHandler(
+ new ExponentialBackOff())
+ .setSleeper(sleeper);
+ request.setInterceptor(wrappedCredential);
+
+ request.setConnectTimeout(3 * 60000); // 3 minutes connect timeout
+ request.setReadTimeout(3 * 60000); // 3 minutes read timeout
+
+ request.setUnsuccessfulResponseHandler(
+ new HttpUnsuccessfulResponseHandler() {
+ public boolean handleResponse(
+ HttpRequest request,
+ HttpResponse response,
+ boolean supportsRetry) throws IOException {
+
+ LOG.info("RetryHandler: " + CharStreams.toString(
+ new InputStreamReader(response.getContent())));
+
+ if (wrappedCredential.handleResponse(
+ request, response, supportsRetry)) {
+ // If credential decides it can handle it,
+ // the return code or message indicated
+ // something specific to authentication,
+ // and no backoff is desired.
+
+ LOG.info("Requested: " + request.getUrl().toString());
+ return true;
+ } else if (backoffHandler.handleResponse(
+ request, response, supportsRetry)) {
+
+ // Otherwise, we defer to the judgement of
+ // our internal backoff handler.
+ LOG.info("Retrying " + request.getUrl());
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+ request.setIOExceptionHandler(
+ new HttpBackOffIOExceptionHandler(new ExponentialBackOff())
+ .setSleeper(sleeper));
+ }
+
+
+}
\ No newline at end of file
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java
new file mode 100644
index 0000000..1124bb5
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java
@@ -0,0 +1,54 @@
+package com.google.android.work.emmnotifications;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.HttpRequestInitializer;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.pubsub.Pubsub;
+import com.google.api.services.pubsub.PubsubScopes;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Creates a Pubsub client with the service account.
+ */
+public class ServiceAccountConfiguration {
+
+ private static final JsonFactory JSON_FACTORY =
+ JacksonFactory.getDefaultInstance();
+
+ public static Pubsub createPubsubClient(String serviceAccountEmail, String privateKeyFilePath)
+ throws IOException, GeneralSecurityException {
+ HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
+ GoogleCredential credential = new GoogleCredential.Builder()
+ .setTransport(transport)
+ .setJsonFactory(JSON_FACTORY)
+ .setServiceAccountScopes(PubsubScopes.all())
+
+ // Obtain this from the "APIs & auth" -> "Credentials"
+ // section in the Google Developers Console:
+ // https://console.developers.google.com/
+ // (and put the e-mail address into your system property obviously)
+ .setServiceAccountId(serviceAccountEmail)
+
+ // Download this file from "APIs & auth" -> "Credentials"
+ // section in the Google Developers Console:
+ // https://console.developers.google.com/
+ .setServiceAccountPrivateKeyFromP12File(new File(privateKeyFilePath))
+ .build();
+
+
+ // Please use custom HttpRequestInitializer for automatic
+ // retry upon failures. We provide a simple reference
+ // implementation in the "Retry Handling" section.
+ HttpRequestInitializer initializer =
+ new RetryHttpInitializerWrapper(credential);
+ return new Pubsub.Builder(transport, JSON_FACTORY, initializer)
+ .setApplicationName("PubSub Example")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java
new file mode 100644
index 0000000..e0273ce
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java
@@ -0,0 +1,157 @@
+package com.google.android.work.emmnotifications;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import static com.google.api.client.util.Preconditions.checkArgument;
+import static com.google.api.client.util.Preconditions.checkNotNull;
+
+/**
+ * Loads settings from settings.properties or file specified in DEVELOPER_CONSOLE_SETTINGS
+ * env variable.
+ */
+public class Settings {
+ private static final String EMAIL = "ServiceAccountEmail";
+ private static final String KEY = "ServiceAccountP12KeyFile";
+ private static final String TOPIC_NAME = "TopicName";
+ private static final String SUBSCRIPTION_NAME = "SubscriptionName";
+ private static final String PROJECT_NAME = "ProjectName";
+ private static final String PUSH_ENDPOINT = "PushEndpoint";
+
+ private static final String EMAIL_DEFAULT = "changeme@gserviceaccount.com";
+ private static final String KEY_DEFAULT = "/path/to/key.p12";
+ private static final String TOPIC_NAME_DEFAULT =
+ "projects/sample-project-name/topics/default";
+ private static final String SUBSCRIPTION_NAME_DEFAULT =
+ "projects/sample-project-name/subscriptions/default";
+ private static final String PROJECT_NAME_DEFAULT = "sample-project-name";
+ private static final String PUSH_ENDPOINT_DEFAULT = "sample.push.endpoint";
+
+ private static final Logger LOG = Logger.getLogger(Settings.class.getName());
+ public static final String SETTINGS_ENV_VAR_NAME = "DEVELOPER_CONSOLE_SETTINGS";
+
+ private static Settings settings = null;
+
+ // See https://cloud.google.com/pubsub/subscriber for more details
+ private final String topicName;
+ private final String subscriptionName;
+
+ // Email address associated with your service account
+ // Obtain this from the "APIs & auth" -> "Credentials"
+ // section in the Google Developers Console:
+ // https://console.developers.google.com/
+ // (and put the e-mail address into your system property obviously)
+ private final String serviceAccountEmail;
+
+ // p12 file of your service account
+ // Download this file from "APIs & auth" -> "Credentials"
+ // section in the Google Developers Console:
+ // https://console.developers.google.com/
+ private final String serviceAccountP12KeyPath;
+
+ // Name of your developer console project
+ private final String projectName;
+
+ // SSL WebHook enabled endpoint, see https://cloud.google.com/pubsub/prereqs#push_endpoints
+ private final String pushEndpoint;
+
+ private Settings(
+ String serviceAccountEmail,
+ String serviceAccountP12KeyPath,
+ String topicName,
+ String subscriptionName,
+ String projectName,
+ String pushEndpoint) {
+ this.serviceAccountEmail = serviceAccountEmail;
+ this.serviceAccountP12KeyPath = serviceAccountP12KeyPath;
+ this.topicName = topicName;
+ this.subscriptionName = subscriptionName;
+ this.projectName = projectName;
+ this.pushEndpoint = pushEndpoint;
+ }
+
+ /**
+ * Returns a singleton instance of Settings
+ * @throws IOException
+ */
+ public static Settings getSettings() throws IOException {
+ if (settings != null) {
+ return settings;
+ }
+
+ String settingsFilePath = "settings.properties";
+
+ String settingsEnvVariable = null;
+ if ((settingsEnvVariable = System.getenv(SETTINGS_ENV_VAR_NAME)) != null) {
+ settingsFilePath = settingsEnvVariable;
+ }
+
+ Properties properties = new Properties();
+ properties.load(new InputStreamReader(new FileInputStream(new File(settingsFilePath))));
+
+ String email = properties.getProperty(EMAIL);
+ String key = properties.getProperty(KEY);
+ String topicName = properties.getProperty(TOPIC_NAME);
+ String subscriptionName = properties.getProperty(SUBSCRIPTION_NAME);
+ String projectName = properties.getProperty(PROJECT_NAME);
+ String pushEndpoint = properties.getProperty(PUSH_ENDPOINT);
+
+ verifyVariable(email, EMAIL, EMAIL_DEFAULT, settingsFilePath);
+ verifyVariable(key, KEY, KEY_DEFAULT, settingsFilePath);
+ verifyVariable(topicName, TOPIC_NAME, TOPIC_NAME_DEFAULT, settingsFilePath);
+ verifyVariable(
+ subscriptionName,
+ SUBSCRIPTION_NAME,
+ SUBSCRIPTION_NAME_DEFAULT,
+ settingsFilePath);
+ verifyVariable(projectName, PROJECT_NAME, PROJECT_NAME_DEFAULT, settingsFilePath);
+ verifyVariable(pushEndpoint, PUSH_ENDPOINT, PUSH_ENDPOINT_DEFAULT, settingsFilePath);
+
+ return (settings = new Settings(
+ email,
+ key,
+ topicName,
+ subscriptionName,
+ projectName,
+ pushEndpoint));
+ }
+
+ private static void verifyVariable(
+ String variable,
+ String key,
+ String defaultValue,
+ String filePath) {
+ checkNotNull(variable, "%s must be set in %s", key, filePath);
+ checkArgument(
+ !variable.equals(defaultValue),
+ "You must specify non-default %s in %s", key, filePath);
+ }
+
+ public String getServiceAccountEmail() {
+ return serviceAccountEmail;
+ }
+
+ public String getServiceAccountP12KeyPath() {
+ return serviceAccountP12KeyPath;
+ }
+
+ public String getSubscriptionName() {
+ return subscriptionName;
+ }
+
+ public String getTopicName() {
+ return topicName;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public String getPushEndpoint() {
+ return pushEndpoint;
+ }
+}
diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java
new file mode 100644
index 0000000..3a26ce9
--- /dev/null
+++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java
@@ -0,0 +1,85 @@
+package com.google.android.work.emmnotifications.notpublic;
+
+import com.google.android.work.emmnotifications.ServiceAccountConfiguration;
+import com.google.android.work.emmnotifications.Settings;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.services.pubsub.Pubsub;
+import com.google.api.services.pubsub.model.AcknowledgeRequest;
+import com.google.api.services.pubsub.model.PubsubMessage;
+import com.google.api.services.pubsub.model.PullRequest;
+import com.google.api.services.pubsub.model.PullResponse;
+import com.google.api.services.pubsub.model.ReceivedMessage;
+import com.google.api.services.pubsub.model.Subscription;
+import com.google.common.collect.ImmutableList;
+
+import java.net.SocketTimeoutException;
+import java.util.logging.Logger;
+
+/**
+ * ---- NOT PUBLIC CODE ----
+ *
+ * Cloud PubSub backends currently 500's when trying to ACK the message therefore this code is NOT PUBLIC.
+ */
+public class PullSubscriber {
+ private static final Logger LOG = Logger.getLogger(PullSubscriber.class.getName());
+
+ public static void main(String[] args) throws Exception {
+ Pubsub client = ServiceAccountConfiguration.createPubsubClient(
+ Settings.getSettings().getServiceAccountEmail(),
+ Settings.getSettings().getServiceAccountP12KeyPath());
+ String topicName = Settings.getSettings().getTopicName();
+ String subName = Settings.getSettings().getSubscriptionName();
+ Subscription subscription = null;
+
+ try {
+ LOG.info("Trying to get subscription " + subName);
+
+ subscription = client
+ .projects()
+ .subscriptions()
+ .get(subName)
+ .execute();
+
+ LOG.info("Got subscription back: " + subscription.toPrettyString());
+ } catch (HttpResponseException e) {
+ throw e;
+ }
+
+ PullRequest pullRequest = new PullRequest().setReturnImmediately(false).setMaxMessages(10);
+
+ LOG.info("Will be polling: " + pullRequest.toPrettyString());
+
+ while (true) {
+ PullResponse response = null;
+ try {
+ response = client
+ .projects()
+ .subscriptions()
+ .pull(subName, pullRequest).execute();
+ } catch (SocketTimeoutException e) {
+ // Something went wrong, try again in a bit
+ LOG.info("Timed out waiting for data, repeating in 1 second...");
+ Thread.sleep(1000);
+ continue;
+ }
+
+ ImmutableList.Builder ackIdsBuilder = ImmutableList.builder();
+ for (ReceivedMessage msg : response.getReceivedMessages()) {
+ PubsubMessage message = msg.getMessage();
+ String ackId = message.getMessageId();
+
+ LOG.info("Will be ack'ing " + ackId);
+ ackIdsBuilder.add(ackId);
+ }
+
+ AcknowledgeRequest ack = new AcknowledgeRequest().setAckIds(ackIdsBuilder.build());
+ client
+ .projects()
+ .subscriptions()
+ .acknowledge(subName, ack)
+ .execute();
+
+ Thread.sleep(500);
+ }
+ }
+}
diff --git a/examples/emm-notifications/src/main/proto/emm_pubsub.proto b/examples/emm-notifications/src/main/proto/emm_pubsub.proto
new file mode 100644
index 0000000..3a2d640
--- /dev/null
+++ b/examples/emm-notifications/src/main/proto/emm_pubsub.proto
@@ -0,0 +1,32 @@
+syntax = "proto2";
+
+option java_package = "com.google.android.work.pubsub";
+
+// A notification delivered to an MDM to inform them of one or more event(s)
+// relating to an enterprise.
+message MdmPushNotification {
+ // Delivers notifications about changes to a product's approval status.
+ repeated ProductApprovalEvent product_approval_event = 1;
+}
+
+// Common information applicable to multiple types of event contained within
+// an MdmPushNotification message that concern a specific enterprise.
+message MdmNotificationEnterpriseEventCommon {
+ // [Required] The ID of the enterprise that the event concerns.
+ optional string enterprise_id = 1;
+
+ // [Required] The time when the event notification was published.
+ optional string event_notification_sent_timestamp = 2;
+}
+
+// An event generated when a product's approval status is changed.
+message ProductApprovalEvent {
+ // [Required] Common information regarding an enterprise event.
+ optional MdmNotificationEnterpriseEventCommon common_event_information = 1;
+
+ // [Required] The ID of the product.
+ optional string product_id = 2;
+
+ // [Required] Whether the product was approved or unapproved.
+ optional bool approved = 3;
+}
\ No newline at end of file