[MM-17145] [MM-18947] [MM-17110] [MM-14926] [MM-18646] Use patched v2.0.6 of react-native-notifications and fix Android badge number (#3382)

* Refactor custom push notification code

* Use react-native-notifications 2.0.6 and patch for scheduled notifs

* Fix patch

* iOS changes

* Fix delete

* Fix setting of badge number on Android

* Undo Reflect removal

* Undo removal of didReceiveRemoteNotification

* Use min importance for push notifs received while app is active

* Correctly set badge number after push notificaiton reply

* Fix tests

* Localize reply action text

* Add getDeliveredNotifications

* Fix identifier check and failing test

* Fix local push notif test for Android > 9
This commit is contained in:
Miguel Alatzar
2019-10-22 11:18:59 -07:00
committed by Elias Nahum
parent 8f0619d083
commit 5e5d3abd79
13 changed files with 772 additions and 315 deletions

View File

@@ -20,7 +20,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.provider.Settings.System;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,7 +32,6 @@ import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.helpers.ApplicationBadgeHelper;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
@@ -44,6 +42,9 @@ public class CustomPushNotification extends PushNotification {
public static final String KEY_TEXT_REPLY = "CAN_REPLY";
public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied";
private NotificationChannel mHighImportanceChannel;
private NotificationChannel mMinImportanceChannel;
private static LinkedHashMap<String,Integer> channelIdToNotificationCount = new LinkedHashMap<String,Integer>();
private static LinkedHashMap<String,List<Bundle>> channelIdToNotification = new LinkedHashMap<String,List<Bundle>>();
private static AppLifecycleFacade lifecycleFacade;
@@ -53,11 +54,12 @@ public class CustomPushNotification extends PushNotification {
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
this.context = context;
createNotificationChannels();
}
public static void clearNotification(Context mContext, int notificationId, String channelId) {
if (notificationId != -1) {
Object objCount = channelIdToNotificationCount.get(channelId);
Object objCount = channelIdToNotificationCount.get(channelId);
Integer count = -1;
if (objCount != null) {
@@ -74,7 +76,6 @@ public class CustomPushNotification extends PushNotification {
if (count != -1) {
int total = CustomPushNotification.badgeCount - count;
int badgeCount = total < 0 ? 0 : total;
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), badgeCount);
CustomPushNotification.badgeCount = badgeCount;
}
}
@@ -87,7 +88,6 @@ public class CustomPushNotification extends PushNotification {
if (mContext != null) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), 0);
}
}
@@ -121,7 +121,7 @@ public class CustomPushNotification extends PushNotification {
}
synchronized (list) {
if (!"clear".equals(type)) {
String senderName = getSenderName(data.getString("sender_name"), data.getString("channel_name"), data.getString("message"));
String senderName = getSenderName(data);
data.putLong("time", new Date().getTime());
data.putString("sender_name", senderName);
data.putString("sender_id", data.getString("sender_id"));
@@ -149,59 +149,142 @@ public class CustomPushNotification extends PushNotification {
digestNotification();
}
@Override
protected void postNotification(int id, Notification notification) {
boolean force = false;
Bundle bundle = notification.extras;
if (bundle != null) {
force = bundle.getBoolean("localTest");
}
if (!mAppLifecycleFacade.isAppVisible() || force) {
super.postNotification(id, notification);
}
}
@Override
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
// First, get a builder initialized with defaults from the core class.
final Notification.Builder notification = new Notification.Builder(mContext);
// If Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Mattermost notifications";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(true);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
notification.setChannelId(CHANNEL_ID);
}
Bundle bundle = mNotificationProps.asBundle();
String version = bundle.getString("version");
addNotificationExtras(notification, bundle);
setNotificationIcons(notification, bundle);
setNotificationMessagingStyle(notification, bundle);
setNotificationChannel(notification, bundle);
setNotificationBadgeIconType(notification);
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
setNotificationSound(notification, notificationPreferences);
setNotificationVibrate(notification, notificationPreferences);
setNotificationBlink(notification, notificationPreferences);
String channelId = bundle.getString("channel_id");
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
String senderId = bundle.getString("sender_id");
String postId = bundle.getString("post_id");
String badge = bundle.getString("badge");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
setNotificationNumber(notification, channelId);
setNotificationDeleteIntent(notification, notificationId);
addNotificationReplyAction(notification, notificationId, bundle);
notification
.setContentIntent(intent)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);
return notification;
}
private void addNotificationExtras(Notification.Builder notification, Bundle bundle) {
Bundle userInfoBundle = bundle.getBundle("userInfo");
if (userInfoBundle == null) {
userInfoBundle = new Bundle();
}
String channelId = bundle.getString("channel_id");
userInfoBundle.putString("channel_id", channelId);
notification.addExtras(userInfoBundle);
}
private void setNotificationIcons(Notification.Builder notification, Bundle bundle) {
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
int smallIconResId = getSmallIconResourceId(smallIcon);
notification.setSmallIcon(smallIconResId);
int largeIconResId = getLargeIconResourceId(largeIcon);
final Resources res = mContext.getResources();
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0 && (largeIconBitmap != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
notification.setLargeIcon(largeIconBitmap);
}
}
private int getSmallIconResourceId(String iconName) {
if (iconName == null) {
iconName = "ic_notification";
}
int resourceId = getIconResourceId(iconName);
if (resourceId == 0) {
iconName = "ic_launcher";
resourceId = getIconResourceId(iconName);
if (resourceId == 0) {
resourceId = android.R.drawable.ic_dialog_info;
}
}
return resourceId;
}
private int getLargeIconResourceId(String iconName) {
if (iconName == null) {
iconName = "ic_launcher";
}
return getIconResourceId(iconName);
}
private int getIconResourceId(String iconName) {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
String defType = "mipmap";
return res.getIdentifier(iconName, defType, packageName);
}
private void setNotificationNumber(Notification.Builder notification, String channelId) {
Integer number = 1;
Object objCount = channelIdToNotificationCount.get(channelId);
if (objCount != null) {
number = (Integer)objCount;
}
notification.setNumber(number);
}
private void setNotificationMessagingStyle(Notification.Builder notification, Bundle bundle) {
Notification.MessagingStyle messagingStyle = getMessagingStyle(bundle);
notification.setStyle(messagingStyle);
}
private Notification.MessagingStyle getMessagingStyle(Bundle bundle) {
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
String senderId = bundle.getString("sender_id");
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
String conversationTitle = getConversationTitle(bundle);
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
return messagingStyle;
}
private String getConversationTitle(Bundle bundle) {
String title = null;
String version = bundle.getString("version");
if (version != null && version.equals("v2")) {
title = channelName;
title = bundle.getString("channel_name");
} else {
title = bundle.getString("title");
}
@@ -211,149 +294,100 @@ public class CustomPushNotification extends PushNotification {
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
Bundle b = bundle.getBundle("userInfo");
if (b == null) {
b = new Bundle();
}
b.putString("channel_id", channelId);
notification.addExtras(b);
int smallIconResId;
int largeIconResId;
if (smallIcon != null) {
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
} else {
smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
}
if (smallIconResId == 0) {
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
if (smallIconResId == 0) {
smallIconResId = android.R.drawable.ic_dialog_info;
}
}
if (largeIcon != null) {
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
} else {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
}
if (badge != null) {
int badgeCount = Integer.parseInt(badge);
CustomPushNotification.badgeCount = badgeCount;
notification.setNumber(badgeCount);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
return title;
}
private void setMessagingStyleConversationTitle(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
if (android.text.TextUtils.isEmpty(senderName)) {
senderName = getSenderName(senderName, channelName, bundle.getString("message"));
senderName = getSenderName(bundle);
}
String personId = senderId;
if (!android.text.TextUtils.isEmpty(channelName)) {
personId = channelId;
if (conversationTitle != null && (!conversationTitle.startsWith("@") || channelName != senderName)) {
messagingStyle.setConversationTitle(conversationTitle);
}
}
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
if (title != null && (!title.startsWith("@") || channelName != senderName)) {
messagingStyle
.setConversationTitle(title);
}
private void addMessagingStyleMessages(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
List<Bundle> bundleList;
String channelId = bundle.getString("channel_id");
List<Bundle> bundleArray = channelIdToNotification.get(channelId);
List<Bundle> list;
if (bundleArray != null) {
list = new ArrayList<Bundle>(bundleArray);
bundleList = new ArrayList<Bundle>(bundleArray);
} else {
list = new ArrayList<Bundle>();
list.add(bundle);
bundleList = new ArrayList<Bundle>();
bundleList.add(bundle);
}
int listCount = list.size() - 1;
for (int i = listCount; i >= 0; i--) {
Bundle data = list.get(i);
int bundleCount = bundleList.size() - 1;
for (int i = bundleCount; i >= 0; i--) {
Bundle data = bundleList.get(i);
String message = data.getString("message");
String previousPersonName = getSenderName(data.getString("sender_name"), channelName, message);
String previousPersonId = data.getString("sender_id");
if (title == null || !android.text.TextUtils.isEmpty(previousPersonName)) {
message = removeSenderFromMessage(previousPersonName, channelName, message);
String senderId = data.getString("sender_id");
Bundle userInfoBundle = data.getBundle("userInfo");
String senderName = getSenderName(data);
if (userInfoBundle != null) {
boolean localPushNotificationTest = userInfoBundle.getBoolean("localTest");
if (localPushNotificationTest) {
senderName = "Test";
}
}
if (conversationTitle == null || !android.text.TextUtils.isEmpty(senderName.trim())) {
message = removeSenderNameFromMessage(message, senderName);
}
long timestamp = data.getLong("time");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle.addMessage(message, data.getLong("time"), previousPersonName);
messagingStyle.addMessage(message, timestamp, senderName);
} else {
Person sender = new Person.Builder()
.setKey(previousPersonId)
.setName(previousPersonName)
.build();
messagingStyle.addMessage(message, data.getLong("time"), sender);
.setKey(senderId)
.setName(senderName)
.build();
messagingStyle.addMessage(message, timestamp, sender);
}
}
}
notification
.setContentIntent(intent)
.setGroupSummary(true)
.setStyle(messagingStyle)
.setSmallIcon(smallIconResId)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);
private void setNotificationChannel(Notification.Builder notification, Bundle bundle) {
// If Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
NotificationChannel notificationChannel = mHighImportanceChannel;
boolean localPushNotificationTest = false;
Bundle userInfoBundle = bundle.getBundle("userInfo");
if (userInfoBundle != null) {
localPushNotificationTest = userInfoBundle.getBoolean("localTest");
}
if (mAppLifecycleFacade.isAppVisible() && !localPushNotificationTest) {
notificationChannel = mMinImportanceChannel;
}
notification.setChannelId(notificationChannel.getId());
}
private void setNotificationBadgeIconType(Notification.Builder notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setBadgeIconType(Notification.BADGE_ICON_SMALL);
}
}
// Let's add a delete intent when the notification is dismissed
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
delIntent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
notification.setDeleteIntent(deleteIntent);
private void setNotificationGroup(Notification.Builder notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setGroup(GROUP_KEY_MESSAGES);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && postId != null) {
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")
.build();
Notification.Action replyAction = new Notification.Action.Builder(
R.drawable.ic_notif_action_reply, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build();
notification
.setShowWhen(true)
.addAction(replyAction);
}
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
notification.setLargeIcon(largeIconBitmap);
.setGroup(GROUP_KEY_MESSAGES)
.setGroupSummary(true);
}
}
private void setNotificationSound(Notification.Builder notification, NotificationPreferences notificationPreferences) {
String soundUri = notificationPreferences.getNotificationSound();
if (soundUri != null) {
if (soundUri != "none") {
@@ -363,65 +397,115 @@ public class CustomPushNotification extends PushNotification {
Uri defaultUri = System.DEFAULT_NOTIFICATION_URI;
notification.setSound(defaultUri, AudioManager.STREAM_NOTIFICATION);
}
}
private void setNotificationVibrate(Notification.Builder notification, NotificationPreferences notificationPreferences) {
boolean vibrate = notificationPreferences.getShouldVibrate();
if (vibrate) {
// use the system default for vibration
// Use the system default for vibration
notification.setDefaults(Notification.DEFAULT_VIBRATE);
}
}
private void setNotificationBlink(Notification.Builder notification, NotificationPreferences notificationPreferences) {
boolean blink = notificationPreferences.getShouldBlink();
if (blink) {
notification.setLights(Color.CYAN, 500, 500);
}
}
return notification;
private void setNotificationDeleteIntent(Notification.Builder notification, int notificationId) {
// Let's add a delete intent when the notification is dismissed
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
delIntent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
notification.setDeleteIntent(deleteIntent);
}
private void addNotificationReplyAction(Notification.Builder notification, int notificationId, Bundle bundle) {
String postId = bundle.getString("post_id");
if (postId == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
mContext,
notificationId,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")
.build();
int icon = R.drawable.ic_notif_action_reply;
CharSequence title = "Reply";
Notification.Action replyAction = new Notification.Action.Builder(icon, title, replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build();
notification
.setShowWhen(true)
.addAction(replyAction);
}
private void notifyReceivedToJS() {
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
public static Integer getMessageCountInChannel(String channelId) {
Object objCount = channelIdToNotificationCount.get(channelId);
if (objCount != null) {
return (Integer)objCount;
}
return 1;
}
private void cancelNotification(Bundle data, int notificationId) {
final String channelId = data.getString("channel_id");
final String numberString = data.getString("badge");
final String badge = data.getString("badge");
CustomPushNotification.badgeCount = Integer.parseInt(numberString);
CustomPushNotification.badgeCount = Integer.parseInt(badge);
CustomPushNotification.clearNotification(mContext.getApplicationContext(), notificationId, channelId);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
private String getSenderName(String senderName, String channelName, String message) {
private String getSenderName(Bundle bundle) {
String senderName = bundle.getString("sender_name");
if (senderName != null) {
return senderName;
} else if (channelName != null && channelName.startsWith("@")) {
}
String channelName = bundle.getString("channel_name");
if (channelName != null && channelName.startsWith("@")) {
return channelName;
}
String name = message.split(":")[0];
if (name != message) {
return name;
String message = bundle.getString("message");
if (message != null) {
String name = message.split(":")[0];
if (name != message) {
return name;
}
}
return " ";
}
private String removeSenderFromMessage(String senderName, String channelName, String message) {
String sender = String.format("%s", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "").replaceFirst(": ", "").trim();
private String removeSenderNameFromMessage(String message, String senderName) {
return message.replaceFirst(senderName, "").replaceFirst(": ", "").trim();
}
private void notificationReceiptDelivery(String ackId, String type) {
ReceiptDelivery.send(context, ackId, type);
}
private void createNotificationChannels() {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mHighImportanceChannel = new NotificationChannel("channel_01", "High Importance", NotificationManager.IMPORTANCE_HIGH);
mHighImportanceChannel.setShowBadge(true);
notificationManager.createNotificationChannel(mHighImportanceChannel);
mMinImportanceChannel = new NotificationChannel("channel_02", "Min Importance", NotificationManager.IMPORTANCE_MIN);
mMinImportanceChannel.setShowBadge(true);
notificationManager.createNotificationChannel(mMinImportanceChannel);
}
}