/** * Copyright 2016-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * <p> * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * <p> * http://aws.amazon.com/apache2.0 * <p> * or in the "license" file accompanying this file. This file 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.amazonaws.mobileconnectors.pinpoint.targeting.notification; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import com.amazonaws.mobileconnectors.pinpoint.analytics.AnalyticsEvent; import com.amazonaws.mobileconnectors.pinpoint.internal.core.PinpointContext; import com.amazonaws.mobileconnectors.pinpoint.internal.core.system.AndroidPreferences; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; /** * NotificationClient is the entry point into the Amazon Mobile Analytics SDK to * handle Pinpoint notifications. */ public class NotificationClient { /** * Intent Key for GCM bundle. */ public String INTENT_SNS_NOTIFICATION_FROM = "from"; /** * Intent Key for GCM bundle data. */ public String INTENT_SNS_NOTIFICATION_DATA = "data"; /** * Result values of handling a pinpoint push message. */ public enum CampaignPushResult { /** * The message wasn't for pinpoint. */ NOT_HANDLED, /** * The SDK handled the message and posted a local notification. */ POSTED_NOTIFICATION, /** * The SDK handled the message, but no notification was posted, since the app was in the foreground. */ APP_IN_FOREGROUND, /** * The SDK handled the message, but no notification was posted, since the app was opted out. */ OPTED_OUT, /** * The SDK handled the message that indicated the local campaign notification was opened. */ NOTIFICATION_OPENED, /** * The SDK handled the message that indicated the local campaign notification was opened. */ SILENT } private static final Log log = LogFactory.getLog(NotificationClient.class); private static final String GCM_TOKEN_PREF_KEY = "AWSPINPOINT.GCMTOKEN"; //Pinpoint private static final String PINPOINT_PUSH_KEY_PREFIX = "pinpoint."; //Notification private static final String GCM_NOTIFICATION_PUSH_KEY_PREFIX = PINPOINT_PUSH_KEY_PREFIX + "notification."; private static final String NOTIFICATION_SILENT_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "silentPush"; private static final String NOTIFICATION_TITLE_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "title"; private static final String NOTIFICATION_BODY_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "body"; private static final String NOTIFICATION_COLOR_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "color"; private static final String NOTIFICATION_ICON_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "icon"; private static final String CAMPAIGN_IMAGE_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "imageUrl"; private static final String CAMPAIGN_IMAGE_ICON_PUSH_KEY = GCM_NOTIFICATION_PUSH_KEY_PREFIX + "imageIconUrl"; //Campaign protected static final String CAMPAIGN_PUSH_KEY_PREFIX = PINPOINT_PUSH_KEY_PREFIX + "campaign."; protected static final String CAMPAIGN_ID_ATTRIBUTE_KEY = "campaign_id"; protected static final String CAMPAIGN_ID_PUSH_KEY = CAMPAIGN_PUSH_KEY_PREFIX + CAMPAIGN_ID_ATTRIBUTE_KEY; protected static final String CAMPAIGN_ACTIVITY_ID_ATTRIBUTE_KEY = "campaign_activity_id"; protected static final String CAMPAIGN_ACTIVITY_ID_PUSH_KEY = CAMPAIGN_PUSH_KEY_PREFIX + CAMPAIGN_ACTIVITY_ID_ATTRIBUTE_KEY; protected static final String CAMPAIGN_TREATMENT_ID_ATTRIBUTE_KEY = "treatment_id"; protected static final String CAMPAIGN_TREATMENT_ID_PUSH_KEY = CAMPAIGN_PUSH_KEY_PREFIX + CAMPAIGN_TREATMENT_ID_ATTRIBUTE_KEY; //Engage Attributes private static final String CAMPAIGN_URL_PUSH_KEY = PINPOINT_PUSH_KEY_PREFIX + "url"; private static final String CAMPAIGN_DEEP_LINK_PUSH_KEY = PINPOINT_PUSH_KEY_PREFIX + "deeplink"; private static final String CAMPAIGN_OPEN_APP_PUSH_KEY = PINPOINT_PUSH_KEY_PREFIX + "openApp"; private static final String REQUEST_ID = "requestId"; private static final int INVALID_RESOURCE = 0; private final PinpointContext pinpointContext; private volatile String theGCMToken; private final List<GCMTokenRegisteredHandler> gcmTokenRegisteredHandlers; private static final String AWS_EVENT_TYPE_OPENED = "_campaign.opened_notification"; private static final String AWS_EVENT_TYPE_RECEIVED_FOREGROUND = "_campaign.received_foreground"; private static final String AWS_EVENT_TYPE_RECEIVED_BACKGROUND = "_campaign.received_background"; public NotificationClient(final PinpointContext pinpointContext) { this.pinpointContext = pinpointContext; this.gcmTokenRegisteredHandlers = new ArrayList<GCMTokenRegisteredHandler>(); this.loadGCMToken(); } public void addGCMTokenRegisteredHandler(final GCMTokenRegisteredHandler handler) { if (handler == null) { throw new IllegalArgumentException("GCMTokenRegisteredHandler cannot be null."); } gcmTokenRegisteredHandlers.add(handler); } public void removeGCMTokenRegisteredHandler(final GCMTokenRegisteredHandler handler) { gcmTokenRegisteredHandlers.remove(handler); } /** * This method should be called once the device token has been received from the GCM api in order to enable * being targeted for campaign push notifications. * * @param deviceToken the GCM device token. */ public void registerGCMDeviceToken(final String deviceToken) { theGCMToken = deviceToken; // Persist the GCM token to shared preferences. final AndroidPreferences prefs = pinpointContext.getSystem().getPreferences(); prefs.putString(GCM_TOKEN_PREF_KEY, deviceToken); for (final GCMTokenRegisteredHandler handler : gcmTokenRegisteredHandlers) { handler.tokenRegistered(deviceToken); } } private void loadGCMToken() { final AndroidPreferences prefs = pinpointContext.getSystem().getPreferences(); // Load the GCM token from shared preferences. theGCMToken = prefs.getString(GCM_TOKEN_PREF_KEY, null); } public String getGCMDeviceToken() { this.loadGCMToken(); return theGCMToken; } private boolean isForeground() { // Gets a list of running processes. ActivityManager am = (ActivityManager) pinpointContext.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses(); // On some versions of android the first item in the list is what runs in the foreground, // but this is not true on all versions. Check the process importance to see if the app // is in the foreground. final String packageName = pinpointContext.getApplicationContext().getPackageName(); for (ActivityManager.RunningAppProcessInfo appProcess : tasks) { final String processName = appProcess.processName; if (ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND == appProcess.importance && packageName.equals(processName)) { return true; } } return false; } private void addGlobalCampaignAttributes(final java.util.Map<java.lang.String, java.lang.String> campaignAttribs) { for (Map.Entry<String, String> entry : campaignAttribs.entrySet()) { if (entry.getValue() != null) { this.pinpointContext.getAnalyticsClient().addGlobalAttribute(entry.getKey(), entry.getValue()); } } } void addCampaignAttributesToEvent(final AnalyticsEvent pushEvent, final java.util.Map<java.lang.String, java.lang.String> campaignAttribs) { for (Map.Entry<String, String> entry : campaignAttribs.entrySet()) { if (entry.getValue() != null) { pushEvent.addAttribute(entry.getKey(), entry.getValue()); } } } private int getNotificationIconResourceId(final String drawableResourceName) { final PackageManager packageManager = pinpointContext.getApplicationContext().getPackageManager(); try { final String packageName = pinpointContext.getApplicationContext().getPackageName(); final ApplicationInfo applicationInfo = packageManager .getApplicationInfo(packageName, PackageManager.GET_META_DATA); final Resources resources = packageManager.getResourcesForApplication(applicationInfo); if (drawableResourceName != null) { final int resId = resources.getIdentifier(drawableResourceName, "drawable", packageName); if (resId != INVALID_RESOURCE) { return resId; } } return applicationInfo.icon; } catch (final PackageManager.NameNotFoundException ex) { log.error("Can't find icon for our application package.", ex); // 0 is an invalid resource id, so use it to indicate failure to retrieve the resource. return INVALID_RESOURCE; } } private Notification createLegacyNotification(final int iconResId, final String title, final String contentText, final PendingIntent contentIntent) { final Notification notification = new Notification(); notification.icon = iconResId; notification.setLatestEventInfo(this.pinpointContext.getApplicationContext(), title, contentText, contentIntent); notification.contentIntent = contentIntent; return notification; } private Constructor<?> notificationBuilderConstructor = null; private Class<?> notificationBuilderClass = null; private Class<?> notificationBigTextStyleClass = null; private Class<?> notificationBigPictureStyleClass = null; private Class<?> notificationStyleClass = null; private Method setContentTitleMethod; private Method setContentTextMethod; private Method setSmallIconMethod; private Method setLargeIconMethod; private Method setContentIntent; private Method setStyleMethod; private Method buildMethod; private Method bigTextMethod; private Method bigPictureMethod; private Method setSummaryMethod; private Method setPriorityMethod; private Method setSoundMethod; private Bitmap notificationImage; private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { @Override protected Bitmap doInBackground(String... urls) { try { return BitmapFactory.decodeStream((new URL(urls[0])).openConnection().getInputStream()); } catch (IOException ex) { log.error("Cannot download or find image for rich notification.", ex); return null; } } } private Notification createNotification(final int iconResId, final String title, final String contentText, final String imageUrl, final String imageIconUrl, final PendingIntent contentIntent) { log.info("Create Notification:" + title + ", Content:" + contentText); if (android.os.Build.VERSION.SDK_INT < 16) { return createLegacyNotification(iconResId, title, contentText, contentIntent); } if (imageUrl != null) { try { notificationImage = new DownloadImageTask().execute(imageUrl).get(); } catch (InterruptedException e) { log.error("Interrupted when downloading image : " + e.getMessage(), e); } catch (ExecutionException e) { log.error("Failed execute download image thread : " + e.getMessage(), e); } } // Use the builder by reflection to use BigTextStyle. if (notificationBuilderClass == null || notificationBigTextStyleClass == null || notificationBigPictureStyleClass == null || notificationStyleClass == null) { try { notificationBuilderClass = Class.forName("android.app.Notification$Builder"); notificationBigTextStyleClass = Class.forName("android.app.Notification$BigTextStyle"); notificationStyleClass = Class.forName("android.app.Notification$Style"); notificationBigPictureStyleClass = Class.forName("android.app.Notification$BigPictureStyle"); } catch (ClassNotFoundException ex) { log.debug("Failed to get notification builder classes by reflection : " + ex.getMessage(), ex); // fall back to creating the legacy notification. return createLegacyNotification(iconResId, title, contentText, contentIntent); } } if (notificationBuilderConstructor == null || setContentTitleMethod == null || setContentTextMethod == null || setSmallIconMethod == null || setLargeIconMethod == null || setContentIntent == null || setStyleMethod == null || buildMethod == null || bigTextMethod == null || bigPictureMethod == null || setSummaryMethod == null || setPriorityMethod == null || setSoundMethod == null) { try { notificationBuilderConstructor = notificationBuilderClass.getDeclaredConstructor(Context.class); setContentTitleMethod = notificationBuilderClass.getDeclaredMethod("setContentTitle", CharSequence.class); setContentTextMethod = notificationBuilderClass.getDeclaredMethod("setContentText", CharSequence.class); setSmallIconMethod = notificationBuilderClass.getDeclaredMethod("setSmallIcon", int.class); setContentIntent = notificationBuilderClass.getDeclaredMethod("setContentIntent", PendingIntent.class); setStyleMethod = notificationBuilderClass.getDeclaredMethod("setStyle", notificationStyleClass); buildMethod = notificationBuilderClass.getDeclaredMethod("build"); bigTextMethod = notificationBigTextStyleClass.getDeclaredMethod("bigText", CharSequence.class); bigPictureMethod = notificationBigPictureStyleClass.getDeclaredMethod("bigPicture", Bitmap.class); setSummaryMethod = notificationBigPictureStyleClass.getDeclaredMethod("setSummaryText", CharSequence.class); setLargeIconMethod = notificationBuilderClass.getDeclaredMethod("setLargeIcon", Bitmap.class); setPriorityMethod = notificationBuilderClass.getDeclaredMethod("setPriority", int.class); setSoundMethod = notificationBuilderClass.getDeclaredMethod("setSound", Uri.class); } catch (NoSuchMethodException ex) { log.debug("Failed to get notification builder methods by reflection. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } } final Object notificationBuilder; final Object bigTextStyle; final Object bigPictureStyle; try { notificationBuilder = notificationBuilderConstructor.newInstance(pinpointContext.getApplicationContext()); bigTextStyle = notificationBigTextStyleClass.newInstance(); bigPictureStyle = notificationBigPictureStyleClass.newInstance(); } catch (InvocationTargetException ex) { log.debug("Can't invoke notification builder constructor. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } catch (IllegalAccessException ex) { log.debug("Can't access notification builder or bigTextStyle or bigPictureStyle classes. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } catch (InstantiationException ex) { log.debug("Exception while instantiating notification builder or bigTextStyle or bigPictureStyle classes. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } try { setContentTitleMethod.invoke(notificationBuilder, title); setContentTextMethod.invoke(notificationBuilder, contentText); setSmallIconMethod.invoke(notificationBuilder, iconResId); setContentIntent.invoke(notificationBuilder, contentIntent); setPriorityMethod.invoke(notificationBuilder, 1); Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); setSoundMethod.invoke(notificationBuilder, defaultSoundUri); if (imageIconUrl != null) { try { setLargeIconMethod.invoke(notificationBuilder, new DownloadImageTask().execute(imageIconUrl).get()); } catch (InterruptedException e) { log.error("Interrupted when downloading image : " + e.getMessage(), e); } catch (ExecutionException e) { log.error("Failed execute download image thread : " + e.getMessage(), e); } } if (imageUrl != null && notificationImage != null) { bigPictureMethod.invoke(bigPictureStyle, notificationImage); setSummaryMethod.invoke(bigPictureStyle, contentText); setStyleMethod.invoke(notificationBuilder, bigPictureStyle); } else { bigTextMethod.invoke(bigTextStyle, contentText); setStyleMethod.invoke(notificationBuilder, bigTextStyle); } return (Notification) buildMethod.invoke(notificationBuilder); } catch (InvocationTargetException ex) { log.debug("Can't invoke notification builder methods. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } catch (IllegalAccessException ex) { log.debug("Can't access notification builder methods. : " + ex.getMessage(), ex); return createLegacyNotification(iconResId, title, contentText, contentIntent); } } private final static String GCM_INTENT_ACTION = "com.google.android.c2dm.intent.RECEIVE"; private final static String FCM_INTENT_ACTION = "com.amazonaws.intent.fcm.NOTIFICATION_OPEN"; private PendingIntent createOpenAppPendingIntent(final Bundle pushBundle, final Class<?> targetClass, String campaignId, int requestId, String intentAction) { PendingIntent contentIntent = null; if (intentAction == GCM_INTENT_ACTION) { contentIntent = PendingIntent.getService( pinpointContext.getApplicationContext(), requestId, this.notificationIntent(pushBundle, campaignId, requestId, GCM_INTENT_ACTION, targetClass), PendingIntent.FLAG_ONE_SHOT); } else { contentIntent = PendingIntent.getBroadcast( pinpointContext.getApplicationContext(), requestId, this.notificationIntent(pushBundle, campaignId, requestId, FCM_INTENT_ACTION, targetClass), PendingIntent.FLAG_ONE_SHOT); PinpointNotificationReceiver.setWeakNotificationClient(this); } return contentIntent; } private Intent notificationIntent(final Bundle pushBundle, String campaignId, int requestId, String intentAction, final Class<?> targetClass) { Intent notificationIntent = new Intent(pinpointContext.getApplicationContext(), targetClass); notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); notificationIntent.setAction(intentAction); notificationIntent.putExtras(pushBundle); notificationIntent.putExtra(INTENT_SNS_NOTIFICATION_FROM, AWS_EVENT_TYPE_OPENED); notificationIntent.putExtra(CAMPAIGN_ID_PUSH_KEY, campaignId); notificationIntent.putExtra(REQUEST_ID, requestId); notificationIntent.setPackage(pinpointContext.getApplicationContext().getPackageName()); return notificationIntent; } private boolean displayNotification(final Bundle pushBundle, final Class<?> targetClass, String imageUrl, String iconImageUrl, Map<String, String> campaignAttributes, String intentAction) { log.info("Display Notification: " + pushBundle.toString()); final String title = pushBundle.getString(NOTIFICATION_TITLE_PUSH_KEY); final String message = pushBundle.getString(NOTIFICATION_BODY_PUSH_KEY); final String campaignId = campaignAttributes.get(CAMPAIGN_ID_ATTRIBUTE_KEY); final String activityId = campaignAttributes.get(CAMPAIGN_ACTIVITY_ID_ATTRIBUTE_KEY); final int requestID = (campaignId + ":" + activityId + ":" + System.currentTimeMillis()).hashCode(); final int iconResId = getNotificationIconResourceId(pushBundle.getString(NOTIFICATION_ICON_PUSH_KEY)); if (iconResId == 0) { return false; } final Notification notification = createNotification( iconResId, title, message, imageUrl, iconImageUrl, this.createOpenAppPendingIntent(pushBundle,targetClass,campaignId,requestID,intentAction)); notification.flags |= Notification.FLAG_AUTO_CANCEL; notification.defaults |= Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE; if (android.os.Build.VERSION.SDK_INT >= 21) { log.info("SDK grater than 21 detected: " + android.os.Build.VERSION.SDK_INT); final String colorString = pushBundle.getString(NOTIFICATION_COLOR_PUSH_KEY); if (colorString != null) { int color; try { color = Color.parseColor(colorString); } catch (final IllegalArgumentException ex) { log.warn("Couldn't parse campaign notification color.", ex); color = 0; } Exception exception = null; try { Field colorField = notification.getClass().getDeclaredField("color"); colorField.setAccessible(true); colorField.set(notification, color); } catch (final IllegalAccessException ex) { exception = ex; } catch (final NoSuchFieldException ex) { exception = ex; } if (exception != null) { log.error("Couldn't set campaign notification color : " + exception.getMessage(), exception); } } } NotificationManager notificationManager = (NotificationManager) pinpointContext.getApplicationContext().getSystemService( Context.NOTIFICATION_SERVICE); notificationManager.notify(requestID, notification); return true; } private boolean openApp() { final Intent launchIntent = pinpointContext.getApplicationContext().getPackageManager() .getLaunchIntentForPackage(pinpointContext.getApplicationContext().getPackageName()); if (launchIntent == null) { log.error("Couldn't get app launch intent for campaign notification."); return false; } launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); launchIntent.setPackage(null); pinpointContext.getApplicationContext().startActivity(launchIntent); return true; } private void openURL(final String url, final boolean noSchemeValidation) { final String validatedUrl; if (url.startsWith("http://") || url.startsWith("https://") || noSchemeValidation) { validatedUrl = url; } else { validatedUrl = "http://" + url; } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(validatedUrl)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); pinpointContext.getApplicationContext().startActivity(intent); } /** * Handles pinpoint FCM push messages by posting a local notification when the app is in the background, * or sending a local broadcast if the app is in the foreground. Also on Api level 19 devices and above, * if local notifications have been disabled and the app is in the background, a local broadcast is sent. * * @param from the from string received by the FCM service, * @param data the bundle received from the FCM service * @return {@link CampaignPushResult}. */ public CampaignPushResult handleFCMCampaignPush(final String from, final Map<String, String> data) { log.info("Handling FCM Notification: " + data.toString()); Bundle bundle = new Bundle(); for (Map.Entry<String, String> entry : data.entrySet()) { bundle.putString(entry.getKey(), entry.getValue()); } return handleCampaignPush(from, bundle, PinpointNotificationReceiver.class, FCM_INTENT_ACTION); } /** * Handles pinpoint GCM push messages by posting a local notification when the app is in the background, * or sending a local broadcast if the app is in the foreground. Also on Api level 19 devices and above, * if local notifications have been disabled and the app is in the background, a local broadcast is sent. * * @param from the from string received by the GCM service * @param data the bundle received from the GCM service * @param serviceClass the class extending GCMListenerService that handles receiving GCM messages. * @return {@link CampaignPushResult}. */ public CampaignPushResult handleGCMCampaignPush(final String from, final Bundle data, final Class<? extends Service> serviceClass) { log.info("Handling GCM Notification: " + data.toString()); return handleCampaignPush(from, data, serviceClass, GCM_INTENT_ACTION); } /*pkg*/ CampaignPushResult handleNotificationOpen(Map<String, String> campaignAttributes, final Bundle data) { // Add any campaign global attributes if (campaignAttributes != null) { //Stop Session if (this.pinpointContext.getSessionClient() != null) { this.pinpointContext.getSessionClient().stopSession(); } addGlobalCampaignAttributes(campaignAttributes); final AnalyticsEvent pushEvent = this.pinpointContext.getAnalyticsClient().createEvent(AWS_EVENT_TYPE_OPENED); this.pinpointContext.getAnalyticsClient().recordEvent(pushEvent); this.pinpointContext.getAnalyticsClient().submitEvents(); final String url = data.getString(CAMPAIGN_URL_PUSH_KEY); if (url != null) { openURL(url, false); return CampaignPushResult.NOTIFICATION_OPENED; } final String deep_link = data.getString(CAMPAIGN_DEEP_LINK_PUSH_KEY); if (deep_link != null) { openURL(deep_link, true); return CampaignPushResult.NOTIFICATION_OPENED; } final String openApp = data.getString(CAMPAIGN_OPEN_APP_PUSH_KEY); if (openApp == null) { log.warn( "No key/value present to determine action for campaign notification, default to open app."); } openApp(); } return CampaignPushResult.NOTIFICATION_OPENED; } private CampaignPushResult handleCampaignPush(final String from, final Bundle data, final Class<?> targetClass, String intentAction) { //Check if push data contains a Campaign Id if (!data.containsKey(CAMPAIGN_ID_PUSH_KEY)) { return CampaignPushResult.NOT_HANDLED; } boolean isAppInForeground = isForeground(); String imageUrl = data.getString(CAMPAIGN_IMAGE_PUSH_KEY); String imageIconUrl = data.getString(CAMPAIGN_IMAGE_ICON_PUSH_KEY); Map<String, String> campaignAttributes = new HashMap<String, String>(); campaignAttributes.put(CAMPAIGN_ID_ATTRIBUTE_KEY, data.getString(CAMPAIGN_ID_PUSH_KEY)); campaignAttributes.put(CAMPAIGN_TREATMENT_ID_ATTRIBUTE_KEY, data.getString(CAMPAIGN_TREATMENT_ID_PUSH_KEY)); campaignAttributes.put(CAMPAIGN_ACTIVITY_ID_ATTRIBUTE_KEY, data.getString(CAMPAIGN_ACTIVITY_ID_PUSH_KEY)); this.pinpointContext.getAnalyticsClient().setCampaignAttributes(campaignAttributes); log.info("Campaign Attributes are:" + campaignAttributes); if (from.equals(AWS_EVENT_TYPE_OPENED)) { return this.handleNotificationOpen(campaignAttributes, data); } if (campaignAttributes != null) { // Create the push event. String eventType = null; if (isAppInForeground) { eventType = AWS_EVENT_TYPE_RECEIVED_FOREGROUND; } else { eventType = AWS_EVENT_TYPE_RECEIVED_BACKGROUND; } final AnalyticsEvent pushEvent = this.pinpointContext.getAnalyticsClient().createEvent(eventType); // Add the campaign attributes. addCampaignAttributesToEvent(pushEvent, campaignAttributes); pushEvent.addAttribute("isAppInForeground", Boolean.toString(isAppInForeground)); try { if (isAppInForeground) { // Notify the caller that the app was in the foreground. return CampaignPushResult.APP_IN_FOREGROUND; } else { // Display a notification with an icon, title, message, image, and default sound. if (data.getString(NOTIFICATION_SILENT_PUSH_KEY).equalsIgnoreCase("1")) { return CampaignPushResult.SILENT; } // App is in the background; attempt to display a notification in the notification center. if (!areAppNotificationsEnabled() || !displayNotification( data, targetClass, imageUrl, imageIconUrl, campaignAttributes, intentAction)) { // Local app notifications have been disabled by the user from Settings -> App Info // or we couldn't display the notification for some reason. pushEvent.addAttribute("isOptedOut", "true"); // We can't post a notification, so delegate to the passed in handler. return CampaignPushResult.OPTED_OUT; } } } finally { this.pinpointContext.getAnalyticsClient().recordEvent(pushEvent); this.pinpointContext.getAnalyticsClient().submitEvents(); } } return CampaignPushResult.POSTED_NOTIFICATION; } private static final String CHECK_OP_NO_THROW = "checkOpNoThrow"; private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; private static final String APP_OPS_MODE_ALLOWED = "MODE_ALLOWED"; private static final String APP_OPS_SERVICE = "APP_OPS_SERVICE"; private Class<?> appOpsClass = null; private Method checkOpNoThrowMethod = null; private Field opPostNotificationField = null; private Field modeAllowedField = null; /** * On devices using Android API level 19 and above this method properly returns whether local * notifications are enabled for the app. For devices before API level 19, this method always * returns true. Disabling notifications was a feature added on devices supporting API Level * 16 and above, so devices from API level 16 to 18 will return true from this method even * when local notifications have been disabled for the app. * * @return true if local notifications are enabled for this app, otherwise false. */ public boolean areAppNotificationsEnabled() { if (android.os.Build.VERSION.SDK_INT < 19) { return true; } final String appOpsServiceName; try { Field appOpsServiceNameField = Context.class.getDeclaredField(APP_OPS_SERVICE); appOpsServiceName = (String) appOpsServiceNameField.get(String.class); } catch (NoSuchFieldException e) { log.error(e.getMessage(), e); return true; } catch (IllegalAccessException e) { log.error(e.getMessage(), e); return true; } Object mAppOps = pinpointContext.getApplicationContext().getSystemService(appOpsServiceName); if (mAppOps == null) { return true; } ApplicationInfo appInfo = pinpointContext.getApplicationContext().getApplicationInfo(); String pkg = pinpointContext.getApplicationContext().getPackageName(); int uid = appInfo.uid; try { if (appOpsClass == null || checkOpNoThrowMethod == null || opPostNotificationField == null || modeAllowedField == null) { appOpsClass = Class.forName(mAppOps.getClass().getName()); checkOpNoThrowMethod = appOpsClass .getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class); opPostNotificationField = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION); modeAllowedField = appOpsClass.getDeclaredField(APP_OPS_MODE_ALLOWED); } final int postNotificationValue = opPostNotificationField.getInt(null); final int opPostNotificationMode = (int) (Integer) checkOpNoThrowMethod.invoke(mAppOps, postNotificationValue, uid, pkg); final int modeAllowed = modeAllowedField.getInt(null); return (modeAllowed == opPostNotificationMode); } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); } catch (NoSuchFieldException e) { log.error(e.getMessage(), e); } catch (InvocationTargetException e) { log.error(e.getMessage(), e); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); } return true; } }