diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotifications.java b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotifications.java index 7e659794c6..91b027f788 100644 --- a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotifications.java +++ b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotifications.java @@ -61,25 +61,23 @@ public void removeAllActiveNotificationsForThread(String threadID) { continue; } String notificationThreadID = data.getString("threadID"); - if (notificationThreadID != null && - notificationThreadID.equals(threadID)) { + String notificationGroup = notification.getNotification().getGroup(); + if (threadID.equals(notificationThreadID) || + threadID.equals(notificationGroup)) { notificationManager.cancel(notification.getTag(), notification.getId()); } } } @ReactMethod - public void getInitialNotification(Promise promise) { - Bundle initialNotification = - getCurrentActivity().getIntent().getParcelableExtra("message"); - if (initialNotification == null) { + public void getInitialNotificationThreadID(Promise promise) { + String initialNotificationThreadID = + getCurrentActivity().getIntent().getStringExtra("threadID"); + if (initialNotificationThreadID == null) { promise.resolve(null); return; } - WritableMap jsReadableNotification = - CommAndroidNotificationParser.parseRemoteMessageToJSMessage( - initialNotification); - promise.resolve(jsReadableNotification); + promise.resolve(initialNotificationThreadID); } @ReactMethod diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsEventEmitter.java b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsEventEmitter.java index 270bd27c45..33b03b4ad1 100644 --- a/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsEventEmitter.java +++ b/native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsEventEmitter.java @@ -74,18 +74,13 @@ private void sendEventToJS(String eventName, Object body) { } private void sendInitialNotificationFromIntentToJS(Intent intent) { - Bundle initialNotification = intent.getParcelableExtra("message"); - if (initialNotification == null) { + String initialNotificationThreadID = intent.getStringExtra("threadID"); + if (initialNotificationThreadID == null) { return; } - WritableMap jsReadableNotification = - CommAndroidNotificationParser.parseRemoteMessageToJSMessage( - initialNotification); - if (jsReadableNotification != null) { - sendEventToJS( - COMM_ANDROID_NOTIFICATIONS_NOTIFICATION_OPENED, - jsReadableNotification); - } + sendEventToJS( + COMM_ANDROID_NOTIFICATIONS_NOTIFICATION_OPENED, + initialNotificationThreadID); } private class CommAndroidNotificationsTokenReceiver diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java index bc5a03384d..83816e629b 100644 --- a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java +++ b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java @@ -153,13 +153,19 @@ private boolean isAppInForeground() { Lifecycle.State.RESUMED; } + private boolean notificationGroupingSupported() { + // Comm doesn't support notification grouping for clients running + // Android versions older than 23 + return android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M; + } + private void handleNotificationRescind(RemoteMessage message) { String setUnreadStatus = message.getData().get(SET_UNREAD_STATUS_KEY); + String threadID = message.getData().get(THREAD_ID_KEY); if ("true".equals(setUnreadStatus)) { File sqliteFile = this.getApplicationContext().getDatabasePath("comm.sqlite"); if (sqliteFile.exists()) { - String threadID = message.getData().get(THREAD_ID_KEY); GlobalDBSingleton.scheduleOrRun(() -> { ThreadOperations.updateSQLiteUnreadStatus( sqliteFile.getPath(), threadID, false); @@ -171,13 +177,55 @@ private void handleNotificationRescind(RemoteMessage message) { } } String rescindID = message.getData().get(RESCIND_ID_KEY); + boolean groupSummaryPresent = false; + boolean threadGroupPresent = false; + for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { String tag = notification.getTag(); + boolean isGroupMember = + threadID.equals(notification.getNotification().getGroup()); + boolean isGroupSummary = + (notification.getNotification().flags & + Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY; if (tag != null && tag.equals(rescindID)) { notificationManager.cancel(notification.getTag(), notification.getId()); + } else if (isGroupMember && isGroupSummary) { + groupSummaryPresent = true; + } else if (isGroupMember) { + threadGroupPresent = true; } } + + if (groupSummaryPresent && !threadGroupPresent) { + notificationManager.cancel(threadID, threadID.hashCode()); + } + } + + private void addToThreadGroupAndDisplay( + String notificationID, + NotificationCompat.Builder notificationBuilder, + String threadID) { + + notificationBuilder = + notificationBuilder.setGroup(threadID).setGroupAlertBehavior( + NotificationCompat.GROUP_ALERT_CHILDREN); + + NotificationCompat.Builder groupSummaryNotificationBuilder = + new NotificationCompat.Builder(this.getApplicationContext()) + .setChannelId(CHANNEL_ID) + .setSmallIcon(R.drawable.notif_icon) + .setContentIntent( + this.createStartMainActivityAction(threadID, threadID)) + .setGroup(threadID) + .setGroupSummary(true) + .setAutoCancel(true) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN); + + notificationManager.notify( + notificationID, notificationID.hashCode(), notificationBuilder.build()); + notificationManager.notify( + threadID, threadID.hashCode(), groupSummaryNotificationBuilder.build()); } private void displayNotification(RemoteMessage message) { @@ -198,9 +246,6 @@ private void displayNotification(RemoteMessage message) { Bundle data = new Bundle(); data.putString(THREAD_ID_KEY, threadID); - PendingIntent startMainActivityAction = - this.createStartMainActivityAction(message); - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this.getApplicationContext()) .setDefaults(Notification.DEFAULT_ALL) @@ -210,25 +255,35 @@ private void displayNotification(RemoteMessage message) { .setVibrate(VIBRATION_SPEC) .setSmallIcon(R.drawable.notif_icon) .setLargeIcon(displayableNotificationLargeIcon) - .setAutoCancel(true) - .setContentIntent(startMainActivityAction); + .setAutoCancel(true); if (title != null) { notificationBuilder = notificationBuilder.setContentTitle(title); } - notificationManager.notify(id, id.hashCode(), notificationBuilder.build()); + + if (threadID != null) { + notificationBuilder = notificationBuilder.setContentIntent( + this.createStartMainActivityAction(id, threadID)); + } + + if (!this.notificationGroupingSupported() || threadID == null) { + notificationManager.notify( + id, id.hashCode(), notificationBuilder.build()); + return; + } + this.addToThreadGroupAndDisplay(id, notificationBuilder, threadID); } - private PendingIntent createStartMainActivityAction(RemoteMessage message) { + private PendingIntent + createStartMainActivityAction(String notificationID, String threadID) { Intent intent = new Intent(this.getApplicationContext(), MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra( - "message", serializeMessageDataForIntentAttachment(message)); + intent.putExtra("threadID", threadID); return PendingIntent.getActivity( this.getApplicationContext(), - message.getData().get(NOTIF_ID_KEY).hashCode(), + notificationID.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); } diff --git a/native/push/android.js b/native/push/android.js index d5f330445f..942e7e1e66 100644 --- a/native/push/android.js +++ b/native/push/android.js @@ -13,7 +13,7 @@ type CommAndroidNotificationsConstants = { type CommAndroidNotificationsModuleType = { +removeAllActiveNotificationsForThread: (threadID: string) => void, - +getInitialNotification: () => Promise, + +getInitialNotificationThreadID: () => Promise, +createChannel: ( channelID: string, name: string, @@ -66,7 +66,7 @@ function handleAndroidMessage( function getCommAndroidNotificationsEventEmitter(): NativeEventEmitter<{ commAndroidNotificationsToken: [string], commAndroidNotificationsMessage: [AndroidMessage], - commAndroidNotificationsNotificationOpened: [AndroidMessage], + commAndroidNotificationsNotificationOpened: [string], }> { return new NativeEventEmitter(CommAndroidNotificationsEventEmitter); } diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js index 6c56022939..ab0cf4782b 100644 --- a/native/push/push-handler.react.js +++ b/native/push/push-handler.react.js @@ -389,10 +389,10 @@ class PushHandler extends React.PureComponent { return; } this.initialAndroidNotifHandled = true; - const initialNotif = - await CommAndroidNotifications.getInitialNotification(); - if (initialNotif) { - await this.androidNotificationOpened(initialNotif); + const initialNotifThreadID = + await CommAndroidNotifications.getInitialNotificationThreadID(); + if (initialNotifThreadID) { + await this.androidNotificationOpened(initialNotifThreadID); } } @@ -572,9 +572,8 @@ class PushHandler extends React.PureComponent { }); } - androidNotificationOpened = async (notificationOpen: AndroidMessage) => { + androidNotificationOpened = async (threadID: string) => { this.onPushNotifBootsApp(); - const { threadID } = notificationOpen; this.onPressNotificationForThread(threadID, true); };