//
// Copyright 2017 Amazon.com, Inc. or its affiliates (Amazon). All Rights Reserved.
//
// Code generated by AWS Mobile Hub. Amazon gives unlimited permission to
// copy, distribute and modify it.
//
// Source code generated from template: aws-my-sample-app-android v0.15
//
package com.amazonaws.mobile.push;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.amazonaws.AmazonClientException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.CreatePlatformEndpointRequest;
import com.amazonaws.services.sns.model.CreatePlatformEndpointResult;
import com.amazonaws.services.sns.model.SetEndpointAttributesRequest;
import com.amazonaws.services.sns.model.SubscribeRequest;
import com.amazonaws.services.sns.model.SubscribeResult;
import com.amazonaws.services.sns.model.UnsubscribeRequest;
import com.amazonaws.mobile.util.ThreadUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* The Push Manager registers the GCM device token in Amazon SNS. The result of this
* registration process is an Amazon SNS Endpoint ARN, which can be used to send
* push notifications directly to a specific device. The Push Manager also manages
* Amazon SNS topic subscriptions, allowing the app to subscribe to Amazon SNS topics,
* which let you target groups of devices with push notifications.
*/
public class PushManager implements GCMTokenHelper.GCMTokenUpdateObserver {
public interface PushStateListener {
void onPushStateChange(PushManager pushManager, boolean isEnabled);
}
private static final String LOG_TAG = PushManager.class.getSimpleName();
// Name of the shared preferences
private static final String SHARED_PREFS_FILE_NAME = PushManager.class.getName();
// Keys in shared preferences
private static final String SHARED_PREFS_PUSH_ENABLED = "pushEnabled";
private static final String SHARED_PREFS_KEY_ENDPOINT_ARN = "endpointArn";
private static final String SHARED_PREFS_PREVIOUS_PLATFORM_APPLICATION = "previousPlatformApp";
// Constants for SNS
private static final String SNS_PROTOCOL_APPLICATION = "application";
private static final String SNS_ENDPOINT_ATTRIBUTE_ENABLED = "Enabled";
private static PushStateListener pushStateListener;
private final AmazonSNS sns;
private final SharedPreferences sharedPreferences;
private final GCMTokenHelper gcmTokenHelper;
private final String platformApplicationArn;
private String endpointArn;
private boolean shouldEnablePush;
private boolean pushEnabled;
private Boolean previousPushState = null;
private final String defaultTopicArn;
/** Map of topic ARN to SnsTopic. */
private Map<String, SnsTopic> topics;
/**
* Constructor.
* @param context application context
* @param gcmTokenHelper the manager of the gcm token.
* @param provider credentials provider
* @param platformApplicationArn Amazon SNS platform application identifier
* @param clientConfiguration client configuration for Amazon SNS client
* @param defaultTopicArn the default topic ARN.
* @param topicArns additional topic ARNs to register not including the default topic arn.
* @param region the AWS region for Push feature
*/
public PushManager(final Context context,
final GCMTokenHelper gcmTokenHelper,
final AWSCredentialsProvider provider,
final String platformApplicationArn,
final ClientConfiguration clientConfiguration,
final String defaultTopicArn,
final String[] topicArns,
final Regions region) {
sharedPreferences = context.getSharedPreferences(SHARED_PREFS_FILE_NAME,
Context.MODE_PRIVATE);
this.gcmTokenHelper = gcmTokenHelper;
this.platformApplicationArn = platformApplicationArn;
this.defaultTopicArn = defaultTopicArn;
sns = new AmazonSNSClient(provider, clientConfiguration);
sns.setRegion(Region.getRegion(region));
topics = new TreeMap<String, SnsTopic>();
setTopics(topicArns);
// Avoid the situation where a previous download/build of the sample app has
// been run in a re-used emulator and the platform application arn changed.
final String previousPlatformApp =
sharedPreferences.getString(SHARED_PREFS_PREVIOUS_PLATFORM_APPLICATION, "");
if (!previousPlatformApp.equalsIgnoreCase(platformApplicationArn)) {
Log.d(LOG_TAG, "SNS platform application ARN changed or not set. Triggering SNS endpoint refresh.");
endpointArn = "";
// clear shared preferences.
sharedPreferences.edit().clear().apply();
pushEnabled = false;
shouldEnablePush = true;
} else {
endpointArn = sharedPreferences.getString(SHARED_PREFS_KEY_ENDPOINT_ARN, "");
pushEnabled = sharedPreferences.getBoolean(SHARED_PREFS_PUSH_ENABLED, false);
shouldEnablePush = pushEnabled;
}
gcmTokenHelper.addTokenUpdateObserver(this);
}
@Override
public void onGCMTokenUpdate(final String gcmToken, final boolean didTokenChange) {
if (didTokenChange || !isRegistered()) {
try {
Log.d(LOG_TAG, "GCM Token changed or SNS endpoint not registered.");
try {
createPlatformArn();
} catch (final AmazonClientException ex) {
Log.e(LOG_TAG, "Error creating platform endpoint ARN: " + ex.getMessage(), ex);
pushEnabled = false;
throw ex;
}
try {
Log.d(LOG_TAG, "Updating push enabled state to " + shouldEnablePush);
setSNSEndpointEnabled(shouldEnablePush);
} catch (final AmazonClientException ex) {
Log.e(LOG_TAG, "Failed to set push enabled state : " + ex, ex);
throw ex;
}
try {
Log.d(LOG_TAG, "Resubscribing to subscribed topics.");
resubscribeToTopics();
} catch (final AmazonClientException ex) {
Log.e(LOG_TAG, "Failed resubscribing to topics : " + ex, ex);
throw ex;
}
} catch (final AmazonClientException ex) {
// Clear the endpoint ARN, regardless of what failed, this will force the app
// to try again the next time the app is started or registerDevice() is called.
endpointArn = "";
Log.e(LOG_TAG, "Push Notifications - FAILED : " + ex, ex);
return;
} finally {
sharedPreferences.edit()
.putString(SHARED_PREFS_PREVIOUS_PLATFORM_APPLICATION, platformApplicationArn)
.putString(SHARED_PREFS_KEY_ENDPOINT_ARN, endpointArn)
// Setting push enabled to whether push should be enabled, so a failure
// will not disable push in shared preferences, and the app will retry
// when restarted.
.putBoolean(SHARED_PREFS_PUSH_ENABLED, shouldEnablePush)
.apply();
informStateListener();
}
}
Log.d(LOG_TAG, "Push Notifications - OK ");
}
@Override
public void onGCMTokenUpdateFailed(final Exception ex) {
Log.e(LOG_TAG, "Push Notifications - FAILED : GCM registration failed : " + ex, ex);
pushEnabled = false;
informStateListener();
}
/**
* Registers this application on this device to receive push notifications from Google cloud messaging.
* Also registers the resulting device token with Amazon SNS. This creates an Amazon SNS platform
* endpoint, which can be used to send push notifications directly to this device. Additionally, this
* also subscribes to the default topic if it has not been explicitly disabled previously by a call to
* {@link #unsubscribeFromTopic(SnsTopic)}.
*
* This method must not be called from the main thread. Use an AsyncTask or other mechanism that ensures
* it is called on a background thread.
*/
public void registerDevice() {
// Updates the GCM token, which triggers {@link #onGCMTokenUpdate(String,boolean)} to create the platform
// arn set push enabled, and re-subscribe to any previously subscribed topics.
gcmTokenHelper.updateGCMToken();
}
private void createPlatformArn() {
final CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest();
request.setPlatformApplicationArn(platformApplicationArn);
request.setToken(gcmTokenHelper.getGCMToken());
final CreatePlatformEndpointResult result = sns.createPlatformEndpoint(request);
endpointArn = result.getEndpointArn();
Log.d(LOG_TAG, "endpoint arn: " + endpointArn);
}
/**
* Associates Amazon SNS topic ARNs to this push manager.
*
* @param topicArns a list of topic ARNs
*/
private void setTopics(final String[] topicArns) {
topics.clear();
topics.put(defaultTopicArn, new SnsTopic(defaultTopicArn, sharedPreferences.getString(defaultTopicArn, "")));
for (String topicArn : topicArns) {
topics.put(topicArn, new SnsTopic(topicArn, sharedPreferences.getString(topicArn, "")));
}
}
private void resubscribeToTopics() {
// Subscribe to all topics that are enabled in shared preferences.
final SnsTopic defaultTopic = getDefaultTopic();
for (final SnsTopic topic : topics.values()) {
final String topicSharedPrefValue =
sharedPreferences.getString(topic.getTopicArn(), null);
if (topicSharedPrefValue == null) {
// The shared preference didn't exist, for the default topic we should auto-subscribe.
if (topic == defaultTopic) {
subscribeToTopic(topic);
Log.d(LOG_TAG, "Push Notifications - Registered for default topic: " + topic.getDisplayName());
}
} else if (!topicSharedPrefValue.equals("")){
// the shared preference existed and the topic was enabled so we should subscribe.
subscribeToTopic(topic);
Log.d(LOG_TAG, "Push Notifications - Registered for topic: " + topic.getDisplayName());
}
}
}
/**
* Subscribes to a given Amazon SNS topic. This method must not be called from the main thread.
*
* @param topic topic to subscribe to
*/
public void subscribeToTopic(final SnsTopic topic) {
final SubscribeRequest request = new SubscribeRequest();
request.setEndpoint(endpointArn);
request.setTopicArn(topic.getTopicArn());
request.setProtocol(SNS_PROTOCOL_APPLICATION);
final SubscribeResult result = sns.subscribe(request);
// update topic and save subscription in shared preferences
final String subscriptionArn = result.getSubscriptionArn();
topic.setSubscriptionArn(subscriptionArn);
sharedPreferences.edit().putString(topic.getTopicArn(), subscriptionArn).apply();
}
/**
* Unsubscribes from a given Amazon SNS topic. This method must not be called from the main thread.
*
* @param topic topic to unsubscribe from
*/
public void unsubscribeFromTopic(final SnsTopic topic) {
// Rely on the status stored locally even though it's likely that the device is
// subscribed to a topic, but the subscription arn is lost, say due to clearing app data.
if (!topic.isSubscribed()) {
return;
}
final UnsubscribeRequest request = new UnsubscribeRequest();
request.setSubscriptionArn(topic.getSubscriptionArn());
sns.unsubscribe(request);
// update topic and save subscription in shared preferences
topic.setSubscriptionArn("");
sharedPreferences.edit().putString(topic.getTopicArn(), "").apply();
}
private void setSNSEndpointEnabled(final boolean enabled) {
Map<String, String> attr = new HashMap<String, String>();
attr.put(SNS_ENDPOINT_ATTRIBUTE_ENABLED, String.valueOf(enabled));
SetEndpointAttributesRequest request = new SetEndpointAttributesRequest();
request.setEndpointArn(endpointArn);
request.setAttributes(attr);
sns.setEndpointAttributes(request);
Log.d(LOG_TAG, String.format("Set push %s for endpoint arn: %s",
enabled ? "enabled" : "disabled", endpointArn));
this.pushEnabled = enabled;
}
/**
* Changes push notification Amazon SNS endpoint status. This method must not be called from the main thread.
*
* @param enabled whether to enable the push notification endpoint
*/
public void setPushEnabled(final boolean enabled) {
shouldEnablePush = enabled;
setSNSEndpointEnabled(enabled);
informStateListener();
sharedPreferences.edit()
.putBoolean(SHARED_PREFS_PUSH_ENABLED, enabled)
.putString(SHARED_PREFS_PREVIOUS_PLATFORM_APPLICATION, platformApplicationArn)
.apply();
}
/**
* Gets whether the device has been registered. If not registered,
* registerDevice() should be invoked.
*
* @return true if registered, false otherwise
*/
public boolean isRegistered() {
return endpointArn != null && !endpointArn.isEmpty();
}
/**
* Gets the device's Amazon SNS endpoint ARN. This endpoint identifier can be
* used to send push notifications directly to this device, from the Amazon SNS
* console, or from another mobile app, if the app has permissions in IAM to
* publish messages to Amazon SNS.
*/
public String getEndpointArn() {
return endpointArn;
}
/**
* Gets a Map of topics that this push manager has, including the default topic.
*
* @return a Map of topic ARNs to SnsTopic objects.
*/
public Map<String, SnsTopic> getTopics() {
return Collections.unmodifiableMap(topics);
}
/**
* Get the default topic.
* @return the default SNS topic.
*/
public SnsTopic getDefaultTopic() {
return topics.get(defaultTopicArn);
}
/**
* Sets a listener to be informed when push notifications are enabled or disabled.
* @param listener listener
*/
public static void setPushStateListener(final PushStateListener listener) {
PushManager.pushStateListener = listener;
}
private void informStateListener() {
if (previousPushState == null || pushEnabled != previousPushState) {
previousPushState = pushEnabled;
if (pushStateListener == null) {
return;
}
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(LOG_TAG, "PushStateListener: State changed to : " +
(pushEnabled ? "PUSH ENABLED" : "PUSH DISABLED"));
try {
pushStateListener.onPushStateChange(PushManager.this, pushEnabled);
Log.d(LOG_TAG, "PushStateListener:onPushStateChange ok");
} catch (final Exception e) {
Log.e(LOG_TAG, "PushStateListener:onPushStateChange Failed : " + e.getMessage(), e);
}
}
});
}
}
/**
* Gets whether the device is enabled for push notification.
*
* @return true if enabled, false otherwise
*/
public boolean isPushEnabled() {
return pushEnabled;
}
}