| |
| package org.wordpress.android; |
| |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.graphics.Bitmap; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.preference.PreferenceManager; |
| import android.support.annotation.NonNull; |
| import android.support.v4.app.NotificationCompat; |
| import android.support.v4.app.NotificationManagerCompat; |
| import android.support.v4.util.ArrayMap; |
| import android.text.TextUtils; |
| |
| import com.google.android.gms.gcm.GcmListenerService; |
| import com.simperium.client.BucketObjectMissingException; |
| |
| import org.apache.commons.lang.StringEscapeUtils; |
| import org.wordpress.android.analytics.AnalyticsTracker; |
| import org.wordpress.android.analytics.AnalyticsTracker.Stat; |
| import org.wordpress.android.analytics.AnalyticsTrackerMixpanel; |
| import org.wordpress.android.models.AccountHelper; |
| import org.wordpress.android.models.CommentStatus; |
| import org.wordpress.android.models.Note; |
| import org.wordpress.android.ui.main.WPMainActivity; |
| import org.wordpress.android.ui.notifications.NotificationDismissBroadcastReceiver; |
| import org.wordpress.android.ui.notifications.NotificationEvents; |
| import org.wordpress.android.ui.notifications.NotificationsListFragment; |
| import org.wordpress.android.ui.notifications.utils.NotificationsUtils; |
| import org.wordpress.android.ui.notifications.utils.SimperiumUtils; |
| import org.wordpress.android.util.AppLog; |
| import org.wordpress.android.util.AppLog.T; |
| import org.wordpress.android.util.HelpshiftHelper; |
| import org.wordpress.android.util.ImageUtils; |
| import org.wordpress.android.util.PhotonUtils; |
| import org.wordpress.android.util.StringUtils; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.net.URLDecoder; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.concurrent.TimeUnit; |
| |
| import de.greenrobot.event.EventBus; |
| |
| public class GCMMessageService extends GcmListenerService { |
| private static final ArrayMap<Integer, Bundle> sActiveNotificationsMap = new ArrayMap<>(); |
| private static String sPreviousNoteId = null; |
| private static long sPreviousNoteTime = 0L; |
| |
| private static final String NOTIFICATION_GROUP_KEY = "notification_group_key"; |
| private static final int PUSH_NOTIFICATION_ID = 10000; |
| private static final int AUTH_PUSH_NOTIFICATION_ID = 20000; |
| public static final int GROUP_NOTIFICATION_ID = 30000; |
| private static final int MAX_INBOX_ITEMS = 5; |
| |
| private static final String PUSH_ARG_USER = "user"; |
| private static final String PUSH_ARG_TYPE = "type"; |
| private static final String PUSH_ARG_TITLE = "title"; |
| private static final String PUSH_ARG_MSG = "msg"; |
| private static final String PUSH_ARG_NOTE_ID = "note_id"; |
| |
| private static final String PUSH_TYPE_COMMENT = "c"; |
| private static final String PUSH_TYPE_LIKE = "like"; |
| private static final String PUSH_TYPE_COMMENT_LIKE = "comment_like"; |
| private static final String PUSH_TYPE_AUTOMATTCHER = "automattcher"; |
| private static final String PUSH_TYPE_FOLLOW = "follow"; |
| private static final String PUSH_TYPE_REBLOG = "reblog"; |
| private static final String PUSH_TYPE_PUSH_AUTH = "push_auth"; |
| private static final String PUSH_TYPE_BADGE_RESET = "badge-reset"; |
| |
| private static final String KEY_CATEGORY_COMMENT_LIKE = "comment-like"; |
| private static final String KEY_CATEGORY_COMMENT_REPLY = "comment-reply"; |
| private static final String KEY_CATEGORY_COMMENT_MODERATE = "comment-moderate"; |
| |
| |
| // Add to the analytics properties map a subset of the push notification payload. |
| private static String[] propertiesToCopyIntoAnalytics = {PUSH_ARG_NOTE_ID, PUSH_ARG_TYPE, "blog_id", "post_id", |
| "comment_id"}; |
| |
| private void synchronizedHandleDefaultPush(String from, @NonNull Bundle data) { |
| // sActiveNotificationsMap being static, we can't just synchronize the method |
| synchronized (GCMMessageService.class) { |
| handleDefaultPush(from, data); |
| } |
| } |
| |
| private void handleDefaultPush(String from, @NonNull Bundle data) { |
| // Ensure Simperium is running so that notes sync |
| SimperiumUtils.configureSimperium(this, AccountHelper.getDefaultAccount().getAccessToken()); |
| |
| long wpcomUserId = AccountHelper.getDefaultAccount().getUserId(); |
| String pushUserId = data.getString(PUSH_ARG_USER); |
| // pushUserId is always set server side, but better to double check it here. |
| if (!String.valueOf(wpcomUserId).equals(pushUserId)) { |
| AppLog.e(T.NOTIFS, "wpcom userId found in the app doesn't match with the ID in the PN. Aborting."); |
| return; |
| } |
| |
| String noteType = StringUtils.notNullStr(data.getString(PUSH_ARG_TYPE)); |
| |
| // Check for wpcom auth push, if so we will process this push differently |
| if (noteType.equals(PUSH_TYPE_PUSH_AUTH)) { |
| handlePushAuth(from, data); |
| return; |
| } |
| |
| if (noteType.equals(PUSH_TYPE_BADGE_RESET)) { |
| handleBadgeResetPN(data); |
| return; |
| } |
| |
| buildAndShowNotificationFromNoteData(data); |
| |
| EventBus.getDefault().post(new NotificationEvents.NotificationsChanged()); |
| } |
| |
| private void buildAndShowNotificationFromNoteData(Bundle data) { |
| |
| if (data == null) |
| return; |
| |
| String noteType = StringUtils.notNullStr(data.getString(PUSH_ARG_TYPE)); |
| |
| String title = StringEscapeUtils.unescapeHtml(data.getString(PUSH_ARG_TITLE)); |
| if (title == null) { |
| title = getString(R.string.app_name); |
| } |
| String message = StringEscapeUtils.unescapeHtml(data.getString(PUSH_ARG_MSG)); |
| String noteId = data.getString(PUSH_ARG_NOTE_ID, ""); |
| |
| /* |
| * if this has the same note_id as the previous notification, and the previous notification |
| * was received within the last second, then skip showing it - this handles duplicate |
| * notifications being shown due to the device being registered multiple times with different tokens. |
| * (still investigating how this could happen - 21-Oct-13) |
| * |
| * this also handles the (rare) case where the user receives rapid-fire sub-second like notifications |
| * due to sudden popularity (post gets added to FP and is liked by many people all at once, etc.), |
| * which we also want to avoid since it would drain the battery and annoy the user |
| * |
| * NOTE: different comments on the same post will have a different note_id, but different likes |
| * on the same post will have the same note_id, so don't assume that the note_id is unique |
| */ |
| long thisTime = System.currentTimeMillis(); |
| if (sPreviousNoteId != null && sPreviousNoteId.equals(noteId)) { |
| long seconds = TimeUnit.MILLISECONDS.toSeconds(thisTime - sPreviousNoteTime); |
| if (seconds <= 1) { |
| AppLog.w(T.NOTIFS, "skipped potential duplicate notification"); |
| return; |
| } |
| } |
| |
| sPreviousNoteId = noteId; |
| sPreviousNoteTime = thisTime; |
| |
| // Update notification content for the same noteId if it is already showing |
| int pushId = 0; |
| for (Integer id : sActiveNotificationsMap.keySet()) { |
| if (id == null) { |
| continue; |
| } |
| Bundle noteBundle = sActiveNotificationsMap.get(id); |
| if (noteBundle != null && noteBundle.getString(PUSH_ARG_NOTE_ID, "").equals(noteId)) { |
| pushId = id; |
| sActiveNotificationsMap.put(pushId, data); |
| break; |
| } |
| } |
| |
| if (pushId == 0) { |
| pushId = PUSH_NOTIFICATION_ID + sActiveNotificationsMap.size(); |
| sActiveNotificationsMap.put(pushId, data); |
| } |
| |
| // Bump Analytics for PNs if "Show notifications" setting is checked (default). Skip otherwise. |
| if (NotificationsUtils.isNotificationsEnabled(this)) { |
| Map<String, Object> properties = new HashMap<>(); |
| if (!TextUtils.isEmpty(noteType)) { |
| // 'comment' and 'comment_pingback' types are sent in PN as type = "c" |
| if (noteType.equals(PUSH_TYPE_COMMENT)) { |
| properties.put("notification_type", "comment"); |
| } else { |
| properties.put("notification_type", noteType); |
| } |
| } |
| |
| bumpPushNotificationsAnalytics(Stat.PUSH_NOTIFICATION_RECEIVED, data, properties); |
| AnalyticsTracker.flush(); |
| } |
| |
| showGroupNotificationForActiveNotificationsMap(pushId, noteId, noteType, data.getString("icon"), title, message); |
| } |
| |
| private void showGroupNotificationForActiveNotificationsMap(int pushId, String noteId, String noteType, |
| String largeIconUri, String title, String message) { |
| |
| // Build the new notification, add group to support wearable stacking |
| NotificationCompat.Builder builder = getNotificationBuilder(title, message); |
| |
| Bitmap largeIconBitmap = getLargeIconBitmap(largeIconUri, shouldCircularizeNoteIcon(noteType)); |
| if (largeIconBitmap != null) { |
| builder.setLargeIcon(largeIconBitmap); |
| } |
| |
| showIndividualNotificationForBuilder(builder, noteType, noteId, pushId); |
| |
| // Also add a group summary notification, which is required for non-wearable devices |
| showGroupNotificationForBuilder(builder, message); |
| } |
| |
| private void addActionsForCommentNotification(NotificationCompat.Builder builder, String noteId) { |
| // Add some actions if this is a comment notification |
| |
| boolean areActionsSet = false; |
| |
| if (SimperiumUtils.getNotesBucket() != null) { |
| try { |
| Note note = SimperiumUtils.getNotesBucket().get(noteId); |
| if (note != null) { |
| //if note can be replied to, we'll always add this action first |
| if (note.canReply()) { |
| addCommentReplyActionForCommentNotification(builder, noteId); |
| } |
| |
| // if the comment is lacking approval, offer moderation actions |
| if (note.getCommentStatus().equals(CommentStatus.UNAPPROVED)) { |
| if (note.canModerate()) { |
| addCommentApproveActionForCommentNotification(builder, noteId); |
| } |
| } else { |
| //else offer REPLY / LIKE actions |
| if (note.canLike()) { |
| addCommentLikeActionForCommentNotification(builder, noteId); |
| } |
| } |
| } |
| areActionsSet = true; |
| } catch (BucketObjectMissingException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| // if we could not set the actions, set the default ones REPLY / LIKE |
| if (!areActionsSet) { |
| addCommentReplyActionForCommentNotification(builder, noteId); |
| addCommentLikeActionForCommentNotification(builder, noteId); |
| } |
| } |
| |
| private void addCommentReplyActionForCommentNotification(NotificationCompat.Builder builder, String noteId) { |
| // adding comment reply action |
| Intent commentReplyIntent = getCommentActionIntent(); |
| commentReplyIntent.addCategory(KEY_CATEGORY_COMMENT_REPLY); |
| commentReplyIntent.putExtra(NotificationsListFragment.NOTE_INSTANT_REPLY_EXTRA, true); |
| if (noteId != null) { |
| commentReplyIntent.putExtra(NotificationsListFragment.NOTE_ID_EXTRA, noteId); |
| } |
| PendingIntent commentReplyPendingIntent = PendingIntent.getActivity(this, 0, commentReplyIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| builder.addAction(R.drawable.ic_reply_white_24dp, getText(R.string.reply), |
| commentReplyPendingIntent); |
| } |
| |
| private void addCommentLikeActionForCommentNotification(NotificationCompat.Builder builder, String noteId) { |
| // adding comment like action |
| Intent commentLikeIntent = getCommentActionIntent(); |
| commentLikeIntent.addCategory(KEY_CATEGORY_COMMENT_LIKE); |
| commentLikeIntent.putExtra(NotificationsListFragment.NOTE_INSTANT_LIKE_EXTRA, true); |
| if (noteId != null) { |
| commentLikeIntent.putExtra(NotificationsListFragment.NOTE_ID_EXTRA, noteId); |
| } |
| PendingIntent commentLikePendingIntent = PendingIntent.getActivity(this, 0, commentLikeIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| builder.addAction(R.drawable.ic_action_like, getText(R.string.like), |
| commentLikePendingIntent); |
| } |
| |
| private void addCommentApproveActionForCommentNotification(NotificationCompat.Builder builder, String noteId) { |
| // adding comment approve action |
| Intent commentApproveIntent = getCommentActionIntent(); |
| commentApproveIntent.addCategory(KEY_CATEGORY_COMMENT_MODERATE); |
| commentApproveIntent.putExtra(NotificationsListFragment.NOTE_INSTANT_APPROVE_EXTRA, true); |
| if (noteId != null) { |
| commentApproveIntent.putExtra(NotificationsListFragment.NOTE_ID_EXTRA, noteId); |
| } |
| PendingIntent commentApprovePendingIntent = PendingIntent.getActivity(this, 0, commentApproveIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| builder.addAction(R.drawable.ic_action_approve, getText(R.string.approve), |
| commentApprovePendingIntent); |
| } |
| |
| private Intent getCommentActionIntent(){ |
| Intent intent = new Intent(this, WPMainActivity.class); |
| intent.putExtra(WPMainActivity.ARG_OPENED_FROM_PUSH, true); |
| intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| intent.setAction("android.intent.action.MAIN"); |
| intent.addCategory("android.intent.category.LAUNCHER"); |
| return intent; |
| } |
| |
| private Bitmap getLargeIconBitmap(String iconUrl, boolean shouldCircularizeIcon){ |
| Bitmap largeIconBitmap = null; |
| if (iconUrl != null) { |
| try { |
| iconUrl = URLDecoder.decode(iconUrl, "UTF-8"); |
| int largeIconSize = getResources().getDimensionPixelSize( |
| android.R.dimen.notification_large_icon_height); |
| String resizedUrl = PhotonUtils.getPhotonImageUrl(iconUrl, largeIconSize, largeIconSize); |
| largeIconBitmap = ImageUtils.downloadBitmap(resizedUrl); |
| if (largeIconBitmap != null && shouldCircularizeIcon) { |
| largeIconBitmap = ImageUtils.getCircularBitmap(largeIconBitmap); |
| } |
| } catch (UnsupportedEncodingException e) { |
| AppLog.e(T.NOTIFS, e); |
| } |
| } |
| return largeIconBitmap; |
| } |
| |
| private NotificationCompat.Builder getNotificationBuilder(String title, String message){ |
| // Build the new notification, add group to support wearable stacking |
| NotificationCompat.Builder builder = new NotificationCompat.Builder(this) |
| .setSmallIcon(R.drawable.notification_icon) |
| .setColor(getResources().getColor(R.color.blue_wordpress)) |
| .setContentTitle(title) |
| .setContentText(message) |
| .setTicker(message) |
| .setAutoCancel(true) |
| .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) |
| .setGroup(NOTIFICATION_GROUP_KEY); |
| |
| return builder; |
| } |
| |
| private void showGroupNotificationForBuilder(NotificationCompat.Builder builder, String message) { |
| |
| if (builder == null) { |
| return; |
| } |
| |
| if (sActiveNotificationsMap.size() > 1) { |
| NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); |
| int noteCtr = 1; |
| for (Bundle pushBundle : sActiveNotificationsMap.values()) { |
| // InboxStyle notification is limited to 5 lines |
| if (noteCtr > MAX_INBOX_ITEMS) { |
| break; |
| } |
| if (pushBundle == null || pushBundle.getString(PUSH_ARG_MSG) == null) { |
| continue; |
| } |
| |
| if (pushBundle.getString(PUSH_ARG_TYPE, "").equals(PUSH_TYPE_COMMENT)) { |
| String pnTitle = StringEscapeUtils.unescapeHtml((pushBundle.getString(PUSH_ARG_TITLE))); |
| String pnMessage = StringEscapeUtils.unescapeHtml((pushBundle.getString(PUSH_ARG_MSG))); |
| inboxStyle.addLine(pnTitle + ": " + pnMessage); |
| } else { |
| String pnMessage = StringEscapeUtils.unescapeHtml((pushBundle.getString(PUSH_ARG_MSG))); |
| inboxStyle.addLine(pnMessage); |
| } |
| |
| noteCtr++; |
| } |
| |
| if (sActiveNotificationsMap.size() > MAX_INBOX_ITEMS) { |
| inboxStyle.setSummaryText(String.format(getString(R.string.more_notifications), |
| sActiveNotificationsMap.size() - MAX_INBOX_ITEMS)); |
| } |
| |
| String subject = String.format(getString(R.string.new_notifications), sActiveNotificationsMap.size()); |
| NotificationCompat.Builder groupBuilder = new NotificationCompat.Builder(this) |
| .setSmallIcon(R.drawable.notification_icon) |
| .setColor(getResources().getColor(R.color.blue_wordpress)) |
| .setGroup(NOTIFICATION_GROUP_KEY) |
| .setGroupSummary(true) |
| .setAutoCancel(true) |
| .setTicker(message) |
| .setContentTitle(getString(R.string.app_name)) |
| .setContentText(subject) |
| .setStyle(inboxStyle); |
| |
| showNotificationForBuilder(groupBuilder, this, GROUP_NOTIFICATION_ID); |
| |
| } else { |
| // Set the individual notification we've already built as the group summary |
| builder.setGroupSummary(true); |
| showNotificationForBuilder(builder, this, GROUP_NOTIFICATION_ID); |
| } |
| } |
| |
| private void showIndividualNotificationForBuilder(NotificationCompat.Builder builder, String noteType, String noteId, int pushId) { |
| if (builder == null) { |
| return; |
| } |
| |
| if (noteType.equals(PUSH_TYPE_COMMENT)) { |
| addActionsForCommentNotification(builder, noteId); |
| } |
| showNotificationForBuilder(builder, this, pushId); |
| } |
| |
| // Displays a notification to the user |
| private void showNotificationForBuilder(NotificationCompat.Builder builder, Context context, int notificationId) { |
| if (builder == null || context == null) { |
| return; |
| } |
| |
| Intent resultIntent = new Intent(this, WPMainActivity.class); |
| resultIntent.putExtra(WPMainActivity.ARG_OPENED_FROM_PUSH, true); |
| resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| resultIntent.setAction("android.intent.action.MAIN"); |
| resultIntent.addCategory("android.intent.category.LAUNCHER"); |
| if (sPreviousNoteId != null) { |
| resultIntent.putExtra(NotificationsListFragment.NOTE_ID_EXTRA, sPreviousNoteId); |
| } |
| |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
| |
| boolean shouldPlaySound = prefs.getBoolean("wp_pref_notification_sound", false); |
| boolean shouldVibrate = prefs.getBoolean("wp_pref_notification_vibrate", false); |
| boolean shouldBlinkLight = prefs.getBoolean("wp_pref_notification_light", false); |
| String notificationSound = prefs.getString("wp_pref_custom_notification_sound", null); //"" if None is selected |
| |
| |
| // use default sound if the legacy sound preference was ON but the custom sound was not selected (null) |
| if (shouldPlaySound && notificationSound == null) { |
| builder.setSound(Uri.parse("content://settings/system/notification_sound")); |
| } else if (!TextUtils.isEmpty(notificationSound)) { |
| builder.setSound(Uri.parse(notificationSound)); |
| } |
| |
| if (shouldVibrate) { |
| builder.setVibrate(new long[]{500, 500, 500}); |
| } |
| if (shouldBlinkLight) { |
| builder.setLights(0xff0000ff, 1000, 5000); |
| } |
| |
| // Call broadcast receiver when notification is dismissed |
| Intent notificationDeletedIntent = new Intent(this, NotificationDismissBroadcastReceiver.class); |
| notificationDeletedIntent.putExtra("notificationId", notificationId); |
| notificationDeletedIntent.setAction(String.valueOf(notificationId)); |
| PendingIntent pendingDeleteIntent = |
| PendingIntent.getBroadcast(context, notificationId, notificationDeletedIntent, 0); |
| builder.setDeleteIntent(pendingDeleteIntent); |
| |
| builder.setCategory(NotificationCompat.CATEGORY_SOCIAL); |
| |
| PendingIntent pendingIntent = PendingIntent.getActivity(context, notificationId, resultIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT); |
| builder.setContentIntent(pendingIntent); |
| NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); |
| notificationManager.notify(notificationId, builder.build()); |
| } |
| |
| private void rebuildAndUpdateNotificationsOnSystemBar(Bundle data) { |
| Bitmap largeIconBitmap = null; |
| // here notify the existing group notification by eliminating the line that is now gone |
| String title = getNotificationTitleOrAppNameFromBundle(data); |
| String message = StringEscapeUtils.unescapeHtml(data.getString(PUSH_ARG_MSG)); |
| |
| NotificationCompat.Builder builder = null; |
| |
| if (sActiveNotificationsMap.size() == 1) { |
| //only one notification remains, so get the proper message for it and re-instate in the system dashboard |
| Bundle remainingNote = sActiveNotificationsMap.values().iterator().next(); |
| if (remainingNote != null) { |
| String remainingNoteTitle = StringEscapeUtils.unescapeHtml(remainingNote.getString(PUSH_ARG_TITLE)); |
| if (!TextUtils.isEmpty(remainingNoteTitle)) { |
| title = remainingNoteTitle; |
| } |
| String remainingNoteMessage = StringEscapeUtils.unescapeHtml(remainingNote.getString(PUSH_ARG_MSG)); |
| if (!TextUtils.isEmpty(remainingNoteMessage)) { |
| message = remainingNoteMessage; |
| } |
| largeIconBitmap = getLargeIconBitmap(remainingNote.getString("icon"), |
| shouldCircularizeNoteIcon(remainingNote.getString(PUSH_ARG_TYPE))); |
| |
| builder = getNotificationBuilder(title, message); |
| |
| String noteType = StringUtils.notNullStr(remainingNote.getString(PUSH_ARG_TYPE)); |
| String noteId = remainingNote.getString(PUSH_ARG_NOTE_ID, ""); |
| if (!sActiveNotificationsMap.isEmpty()) { |
| showIndividualNotificationForBuilder(builder, noteType, noteId, sActiveNotificationsMap.keyAt(0)); |
| } |
| } |
| } |
| |
| if (builder == null) { |
| builder = getNotificationBuilder(title, message); |
| } |
| |
| if (largeIconBitmap == null) { |
| largeIconBitmap = getLargeIconBitmap(data.getString("icon"), shouldCircularizeNoteIcon(PUSH_TYPE_BADGE_RESET)); |
| } |
| |
| if (largeIconBitmap != null) { |
| builder.setLargeIcon(largeIconBitmap); |
| } |
| |
| showGroupNotificationForBuilder(builder, message); |
| |
| } |
| |
| private String getNotificationTitleOrAppNameFromBundle(Bundle data){ |
| String title = StringEscapeUtils.unescapeHtml(data.getString(PUSH_ARG_TITLE)); |
| if (title == null) { |
| title = getString(R.string.app_name); |
| } |
| return title; |
| } |
| |
| // Clear all notifications |
| private void handleBadgeResetPN(Bundle data) { |
| if (data == null || !data.containsKey(PUSH_ARG_NOTE_ID)) { |
| // ignore the reset-badge PN if it's a global one |
| return; |
| } |
| |
| removeNotificationWithNoteIdFromSystemBar(this, data.getString(PUSH_ARG_NOTE_ID, "")); |
| //now that we cleared the specific notif, we can check and make any visual updates |
| if (sActiveNotificationsMap.size() > 0) { |
| rebuildAndUpdateNotificationsOnSystemBar(data); |
| } |
| |
| EventBus.getDefault().post(new NotificationEvents.NotificationsChanged()); |
| } |
| |
| // Show a notification for two-step auth users who sign in from a web browser |
| private void handlePushAuth(String from, Bundle data) { |
| if (data == null) { |
| return; |
| } |
| |
| String pushAuthToken = data.getString("push_auth_token", ""); |
| String title = data.getString("title", ""); |
| String message = data.getString("msg", ""); |
| long expirationTimestamp = Long.valueOf(data.getString("expires", "0")); |
| |
| // No strings, no service |
| if (TextUtils.isEmpty(pushAuthToken) || TextUtils.isEmpty(title) || TextUtils.isEmpty(message)) { |
| return; |
| } |
| |
| // Show authorization intent |
| Intent pushAuthIntent = new Intent(this, WPMainActivity.class); |
| pushAuthIntent.putExtra(WPMainActivity.ARG_OPENED_FROM_PUSH, true); |
| pushAuthIntent.putExtra(NotificationsUtils.ARG_PUSH_AUTH_TOKEN, pushAuthToken); |
| pushAuthIntent.putExtra(NotificationsUtils.ARG_PUSH_AUTH_TITLE, title); |
| pushAuthIntent.putExtra(NotificationsUtils.ARG_PUSH_AUTH_MESSAGE, message); |
| pushAuthIntent.putExtra(NotificationsUtils.ARG_PUSH_AUTH_EXPIRES, expirationTimestamp); |
| pushAuthIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| pushAuthIntent.setAction("android.intent.action.MAIN"); |
| pushAuthIntent.addCategory("android.intent.category.LAUNCHER"); |
| |
| NotificationCompat.Builder builder = new NotificationCompat.Builder(this) |
| .setSmallIcon(R.drawable.notification_icon) |
| .setColor(getResources().getColor(R.color.blue_wordpress)) |
| .setContentTitle(title) |
| .setContentText(message) |
| .setAutoCancel(true) |
| .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) |
| .setPriority(NotificationCompat.PRIORITY_MAX); |
| |
| PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, pushAuthIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT); |
| builder.setContentIntent(pendingIntent); |
| |
| NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); |
| notificationManager.notify(AUTH_PUSH_NOTIFICATION_ID, builder.build()); |
| } |
| |
| @Override |
| public void onMessageReceived(String from, Bundle data) { |
| AppLog.v(T.NOTIFS, "Received Message"); |
| |
| if (data == null) { |
| AppLog.v(T.NOTIFS, "No notification message content received. Aborting."); |
| return; |
| } |
| |
| // Handle helpshift PNs |
| if (TextUtils.equals(data.getString("origin"), "helpshift")) { |
| HelpshiftHelper.getInstance().handlePush(this, new Intent().putExtras(data)); |
| return; |
| } |
| |
| // Handle mixpanel PNs |
| if (data.containsKey("mp_message")) { |
| String mpMessage = data.getString("mp_message"); |
| String title = getString(R.string.app_name); |
| Intent resultIntent = new Intent(this, WPMainActivity.class); |
| resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, resultIntent, |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| AnalyticsTrackerMixpanel.showNotification(this, pendingIntent, |
| R.drawable.notification_icon, title, mpMessage); |
| return; |
| } |
| |
| if (!AccountHelper.isSignedInWordPressDotCom()) { |
| return; |
| } |
| |
| synchronizedHandleDefaultPush(from, data); |
| } |
| |
| // Returns true if the note type is known to have a gravatar |
| public boolean shouldCircularizeNoteIcon(String noteType) { |
| if (TextUtils.isEmpty(noteType)) { |
| return false; |
| } |
| |
| switch (noteType) { |
| case PUSH_TYPE_COMMENT: |
| case PUSH_TYPE_LIKE: |
| case PUSH_TYPE_COMMENT_LIKE: |
| case PUSH_TYPE_AUTOMATTCHER: |
| case PUSH_TYPE_FOLLOW: |
| case PUSH_TYPE_REBLOG: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| public static synchronized void clearNotifications() { |
| sActiveNotificationsMap.clear(); |
| } |
| |
| public static synchronized int getNotificationsCount() { |
| return sActiveNotificationsMap.size(); |
| } |
| |
| public static synchronized boolean hasNotifications() { |
| return !sActiveNotificationsMap.isEmpty(); |
| } |
| |
| // Removes a specific notification from the internal map - only use this when we know |
| // the user has dismissed the app by swiping it off the screen |
| public static synchronized void removeNotification(int notificationId) { |
| sActiveNotificationsMap.remove(notificationId); |
| } |
| |
| // Removes a specific notification from the system bar |
| public static synchronized void removeNotificationWithNoteIdFromSystemBar(Context context, String noteID) { |
| if (context == null || TextUtils.isEmpty(noteID) || !hasNotifications()) { |
| return; |
| } |
| |
| NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); |
| // here we loop with an Iterator as there might be several Notifications with the same Note ID (i.e. likes on the same Note) |
| // so we need to keep cancelling them and removing them from our activeNotificationsMap as we find it suitable |
| for(Iterator<Map.Entry<Integer, Bundle>> it = sActiveNotificationsMap.entrySet().iterator(); it.hasNext(); ) { |
| Map.Entry<Integer, Bundle> row = it.next(); |
| Integer pushId = row.getKey(); |
| Bundle noteBundle = row.getValue(); |
| if (noteBundle.getString(PUSH_ARG_NOTE_ID, "").equals(noteID)) { |
| notificationManager.cancel(pushId); |
| it.remove(); |
| } |
| } |
| |
| if (sActiveNotificationsMap.size() == 0) { |
| notificationManager.cancel(GCMMessageService.GROUP_NOTIFICATION_ID); |
| } |
| } |
| |
| // Removes all app notifications from the system bar |
| public static synchronized void removeAllNotifications(Context context) { |
| if (context == null || !hasNotifications()) { |
| return; |
| } |
| |
| NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); |
| for (Integer pushId : sActiveNotificationsMap.keySet()) { |
| notificationManager.cancel(pushId); |
| } |
| notificationManager.cancel(GCMMessageService.GROUP_NOTIFICATION_ID); |
| |
| clearNotifications(); |
| } |
| |
| // NoteID is the ID if the note in WordPress |
| public static synchronized void bumpPushNotificationsTappedAnalytics(String noteID) { |
| for (int id : sActiveNotificationsMap.keySet()) { |
| Bundle noteBundle = sActiveNotificationsMap.get(id); |
| if (noteBundle.getString(PUSH_ARG_NOTE_ID, "").equals(noteID)) { |
| bumpPushNotificationsAnalytics(Stat.PUSH_NOTIFICATION_TAPPED, noteBundle, null); |
| AnalyticsTracker.flush(); |
| return; |
| } |
| } |
| } |
| |
| // Mark all notifications as tapped |
| public static synchronized void bumpPushNotificationsTappedAllAnalytics() { |
| for (int id : sActiveNotificationsMap.keySet()) { |
| Bundle noteBundle = sActiveNotificationsMap.get(id); |
| bumpPushNotificationsAnalytics(Stat.PUSH_NOTIFICATION_TAPPED, noteBundle, null); |
| } |
| AnalyticsTracker.flush(); |
| } |
| |
| private static void bumpPushNotificationsAnalytics(Stat stat, Bundle noteBundle, |
| Map<String, Object> properties) { |
| // Bump Analytics for PNs if "Show notifications" setting is checked (default). Skip otherwise. |
| if (!NotificationsUtils.isNotificationsEnabled(WordPress.getContext())) { |
| return; |
| } |
| if (properties == null) { |
| properties = new HashMap<>(); |
| } |
| |
| String notificationID = noteBundle.getString(PUSH_ARG_NOTE_ID, ""); |
| if (!TextUtils.isEmpty(notificationID)) { |
| for (String currentPropertyToCopy : propertiesToCopyIntoAnalytics) { |
| if (noteBundle.containsKey(currentPropertyToCopy)) { |
| properties.put("push_notification_" + currentPropertyToCopy, noteBundle.get(currentPropertyToCopy)); |
| } |
| } |
| SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(WordPress.getContext()); |
| String lastRegisteredGCMToken = preferences.getString(NotificationsUtils.WPCOM_PUSH_DEVICE_TOKEN, null); |
| properties.put("push_notification_token", lastRegisteredGCMToken); |
| AnalyticsTracker.track(stat, properties); |
| } |
| } |
| } |