Repository: wordpress-mobile/WordPress-Android (language: kotlin)

Issue:
🤖 Add the content of a comment in the push notification
The push notification should include the content of the comment (like, comment). When the content does not fit [the notification should expand](https://developer.android.com/develop/ui/views/notifications#expandable).


Discussion hints:
nan

Pull request: 🤖 Add the content of a comment in the push notification
Fixes #20099

## Description
This PR adds the whole content of a comment type push notification (as it is passed by the service).
It also changes the comment notification title aligning with iOS.

|Before Android|Current iOS|
|---|---|
|<img width="320" src="https://github.com/wordpress-mobile/WordPress-Android/assets/304044/4d3578ab-fe32-43fb-aad1-c555b80a9851"/>|<img width="320" src="https://github.com/wordpress-mobile/WordPress-Android/assets/304044/d05c0611-9c64-4d7d-904b-4caf96643e29"/>|

|After Expanded|After Lockscreen/Collapsed|
|---|---|
|<img width="320" src="https://github.com/wordpress-mobile/WordPress-Android/assets/304044/004c4bdc-f483-44d7-bcd1-31a20177db91"/>|<img width="320" src="https://github.com/wordpress-mobile/WordPress-Android/assets/304044/d05fddf8-7bd2-46f4-bda4-15a28788fc88"/>|


-----

## To Test:
#### Note: You can use the CI build for testing or a build signed with the production keys
### Comment notification
1. Send a comment notification to yourself either via another account or by using a tool (see pcdRpT-5yH-p2#comment-8854)
2. **Verify** that the compact view of the notification is the expected (e.g. in the lockscreen)
3. **Verify** that the expanded notification contains the first sentences of the comment
#### Note: The comment might be truncated at the end due to the service limitations. An improvement on this would be investigated on the backend.
### Non-Comment notification (follow, like, comment like,...)
1. Send a comment notification to yourself either via another account or by using a tool (see pcdRpT-5yH-p2#comment-8854)
2. **Verify** that the compact view of the notification hasn't changed
3. **Verify** that the expanded notification hasn't changed

<!-- Test instructions per dependency update: https://github.com/wordpress-mobile/WordPress-Android/blob/trunk/docs/test_instructions_per_dependency_update.md -->

-----

## Regression Notes

1. Potential unintended areas of impact

    - PN functionality

5. What I did to test those areas of impact (or what existing automated tests I relied on)

    - Manual testing

6. What automated tests I added (or what prevented me from doing so)

    - Added test cases for the PN title and message selection logic

-----

## PR Submission Checklist:

- [x] I have completed the Regression Notes.
- [x] I have considered adding accessibility improvements for my changes.
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

-----

## Testing Checklist:

- [ ] WordPress.com sites and self-hosted Jetpack sites.
- [ ] Portrait and landscape orientations.
- [ ] Light and dark modes.
- [ ] Fonts: Larger, smaller and bold text.
- [ ] High contrast.
- [ ] Talkback.
- [ ] Languages with large words or with letters/accents not frequently used in English.
- [ ] Right-to-left languages. (Even if translation isn’t complete, formatting should still respect the right-to-left layout)
- [ ] Large and small screen sizes. (Tablet and smaller phones)
- [ ] Multi-tasking: Split screen and Pop-up view. (Android 10 or higher)

Test command: ./gradlew :WordPress:testWordpressVanillaDebugUnitTest --tests "org.wordpress.android.push.NotificationHelperTest"

Fix patch:
diff --git a/WordPress/src/main/java/org/wordpress/android/push/GCMMessageHandler.java b/WordPress/src/main/java/org/wordpress/android/push/GCMMessageHandler.java
index 5495ae79f441..1ecc1ad4ff77 100644
--- a/WordPress/src/main/java/org/wordpress/android/push/GCMMessageHandler.java
+++ b/WordPress/src/main/java/org/wordpress/android/push/GCMMessageHandler.java
@@ -32,6 +32,7 @@
 import org.wordpress.android.ui.notifications.SystemNotificationsTracker;
 import org.wordpress.android.ui.notifications.utils.NotificationsActions;
 import org.wordpress.android.ui.notifications.utils.NotificationsUtils;
+import org.wordpress.android.ui.notifications.utils.NotificationsUtilsWrapper;
 import org.wordpress.android.ui.prefs.AppPrefs;
 import org.wordpress.android.util.AppLog;
 import org.wordpress.android.util.AppLog.T;
@@ -74,10 +75,10 @@ public class GCMMessageHandler {
 
     private static final String PUSH_ARG_TYPE = "type";
     private static final String PUSH_ARG_USER = "user";
-    private static final String PUSH_ARG_TITLE = "title";
-    private static final String PUSH_ARG_MSG = "msg";
+    protected static final String PUSH_ARG_TITLE = "title";
+    protected static final String PUSH_ARG_MSG = "msg";
 
-    private static final String PUSH_TYPE_COMMENT = "c";
+    protected 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";
@@ -100,9 +101,10 @@ public class GCMMessageHandler {
     private final ArrayMap<Integer, Bundle> mActiveNotificationsMap;
     private final NotificationHelper mNotificationHelper;
 
-    @Inject GCMMessageHandler(SystemNotificationsTracker systemNotificationsTracker) {
+    @Inject GCMMessageHandler(SystemNotificationsTracker systemNotificationsTracker,
+                              NotificationsUtilsWrapper notificationsUtilsWrapper) {
         mActiveNotificationsMap = new ArrayMap<>();
-        mNotificationHelper = new NotificationHelper(this, systemNotificationsTracker);
+        mNotificationHelper = new NotificationHelper(this, systemNotificationsTracker, notificationsUtilsWrapper);
     }
 
     synchronized void rebuildAndUpdateNotificationsOnSystemBarForThisNote(Context context,
@@ -120,13 +122,6 @@ synchronized void rebuildAndUpdateNotificationsOnSystemBarForThisNote(Context co
         }
     }
 
-    public synchronized void rebuildAndUpdateNotifsOnSystemBarForRemainingNote(Context context) {
-        if (mActiveNotificationsMap.size() > 0) {
-            Bundle remainingNote = mActiveNotificationsMap.values().iterator().next();
-            mNotificationHelper.rebuildAndUpdateNotificationsOnSystemBar(context, remainingNote);
-        }
-    }
-
     private synchronized Bundle getCurrentNoteBundleForNoteId(String noteId) {
         if (mActiveNotificationsMap.size() > 0) {
             // get the corresponding bundle for this noteId
@@ -278,10 +273,14 @@ public static class NotificationHelper {
         private GCMMessageHandler mGCMMessageHandler;
         private SystemNotificationsTracker mSystemNotificationsTracker;
 
+        private NotificationsUtilsWrapper mNotificationsUtilsWrapper;
+
         NotificationHelper(GCMMessageHandler gCMMessageHandler,
-                           SystemNotificationsTracker systemNotificationsTracker) {
+                           SystemNotificationsTracker systemNotificationsTracker,
+                           NotificationsUtilsWrapper notificationsUtilsWrapper) {
             mGCMMessageHandler = gCMMessageHandler;
             mSystemNotificationsTracker = systemNotificationsTracker;
+            mNotificationsUtilsWrapper = notificationsUtilsWrapper;
         }
 
         void handleDefaultPush(Context context, @NonNull Bundle data, long wpcomUserId) {
@@ -354,11 +353,8 @@ private void buildAndShowNotificationFromNoteData(Context context, Bundle data)
 
             String noteType = StringUtils.notNullStr(data.getString(PUSH_ARG_TYPE));
 
-            String title = StringEscapeUtils.unescapeHtml4(data.getString(PUSH_ARG_TITLE));
-            if (title == null) {
-                title = context.getString(R.string.app_name);
-            }
-            String message = StringEscapeUtils.unescapeHtml4(data.getString(PUSH_ARG_MSG));
+            String title = getNotificationTitle(data, noteType, context.getString(R.string.app_name));
+            String message = getNotificationMessage(data, noteType);
 
             /*
              * if this has the same note_id as the previous notification, and the previous notification
@@ -421,6 +417,45 @@ private void buildAndShowNotificationFromNoteData(Context context, Bundle data)
             );
         }
 
+        @NonNull
+        protected String getNotificationTitle(@NonNull Bundle data,
+                                              @NonNull String noteType,
+                                              @NonNull String defaultTitle) {
+            String title;
+            if (noteType.equals(PUSH_TYPE_COMMENT)) {
+                title = StringEscapeUtils.unescapeHtml4(data.getString(PUSH_ARG_MSG));
+            } else {
+                title = StringEscapeUtils.unescapeHtml4(data.getString(PUSH_ARG_TITLE));
+            }
+            if (title == null) {
+                return defaultTitle;
+            }
+            return title;
+        }
+
+        @NonNull
+        protected String getNotificationMessage(@NonNull Bundle data, @NonNull String noteType) {
+            if (noteType.equals(PUSH_TYPE_COMMENT)) {
+                String noteId = data.getString(PUSH_ARG_NOTE_ID);
+                if (noteId != null) {
+                    Note note = mNotificationsUtilsWrapper.getNoteById(noteId);
+                    if (note != null) {
+                        String summary = note.getCommentSubject();
+                        if (!TextUtils.isEmpty(summary)) {
+                            return summary;
+                        }
+                    }
+                }
+            }
+
+            // Not a comment or the comment content was not retrieved
+            String message = StringEscapeUtils.unescapeHtml4(data.getString(PUSH_ARG_MSG));
+            if (message == null) {
+                return "";
+            }
+            return message;
+        }
+
         private void showNotificationForNoteData(Context context, Bundle noteData, NotificationCompat.Builder builder) {
             String noteType = StringUtils.notNullStr(noteData.getString(PUSH_ARG_TYPE));
             String wpcomNoteID = noteData.getString(PUSH_ARG_NOTE_ID, "");
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
index 2d8ad8c77485..151916d79294 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtils.java
@@ -17,6 +17,7 @@
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.app.NotificationManagerCompat;
 import androidx.preference.PreferenceManager;
@@ -469,6 +470,11 @@ public static boolean buildNoteObjectFromBundleAndSaveIt(Bundle data) {
         return false;
     }
 
+    @Nullable
+    public static Note getNoteById(@Nullable String noteID) {
+        return NotificationsTable.getNoteById(noteID);
+    }
+
     public static Note buildNoteObjectFromBundle(Bundle data) {
         if (data == null) {
             AppLog.e(T.NOTIFS, "Bundle is null! Cannot read '" + GCMMessageService.PUSH_ARG_NOTE_ID + "'.");
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtilsWrapper.kt
index b2ccbf49a396..bef354abe943 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtilsWrapper.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/utils/NotificationsUtilsWrapper.kt
@@ -6,6 +6,7 @@ import org.json.JSONObject
 import org.wordpress.android.fluxc.tools.FormattableContent
 import org.wordpress.android.fluxc.tools.FormattableContentMapper
 import org.wordpress.android.fluxc.tools.FormattableRange
+import org.wordpress.android.models.Note
 import org.wordpress.android.ui.notifications.blocks.NoteBlock
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -69,4 +70,6 @@ class NotificationsUtilsWrapper @Inject constructor(private val formattableConte
 
     fun mapJsonToFormattableContent(blockObject: JSONObject): FormattableContent = NotificationsUtils
         .mapJsonToFormattableContent(formattableContentMapper, blockObject)
+
+    fun getNoteById(noteId: String): Note? = NotificationsUtils.getNoteById(noteId)
 }


Test patch:
diff --git a/WordPress/src/test/java/org/wordpress/android/push/NotificationHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/push/NotificationHelperTest.kt
new file mode 100644
index 000000000000..77430434c066
--- /dev/null
+++ b/WordPress/src/test/java/org/wordpress/android/push/NotificationHelperTest.kt
@@ -0,0 +1,114 @@
+package org.wordpress.android.push
+
+import android.os.Bundle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.apache.commons.text.StringEscapeUtils
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.wordpress.android.BaseUnitTest
+import org.wordpress.android.models.Note
+import org.wordpress.android.push.GCMMessageHandler.PUSH_TYPE_COMMENT
+import org.wordpress.android.push.GCMMessageHandler.PUSH_ARG_MSG
+import org.wordpress.android.push.GCMMessageHandler.PUSH_ARG_TITLE
+import org.wordpress.android.push.GCMMessageService.PUSH_ARG_NOTE_ID
+import org.wordpress.android.ui.notifications.SystemNotificationsTracker
+import org.wordpress.android.ui.notifications.utils.NotificationsUtilsWrapper
+import kotlin.test.assertEquals
+
+private const val PUSH_TYPE_NOT_A_COMMENT = "NotAComment"
+
+@ExperimentalCoroutinesApi
+class NotificationHelperTest : BaseUnitTest() {
+    private val systemNotificationsTracker: SystemNotificationsTracker = mock()
+    private val gcmMessageHandler: GCMMessageHandler = mock()
+    private val notificationsUtilsWrapper = mock<NotificationsUtilsWrapper>()
+    private val notificationHelper =
+        GCMMessageHandler.NotificationHelper(gcmMessageHandler, systemNotificationsTracker, notificationsUtilsWrapper)
+
+    @Test
+    fun `WHEN a PN that is a comment has a message argument THEN then the message is used as title`() {
+        val expectedTitle = "expectedTitle"
+        val defaultTitle = "defaultTitle"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_MSG)).thenReturn(StringEscapeUtils.escapeHtml4(expectedTitle))
+        val title = notificationHelper.getNotificationTitle(mockedBundle, PUSH_TYPE_COMMENT, defaultTitle)
+        assertEquals(expectedTitle, title)
+    }
+
+    @Test
+    fun `WHEN a PN that is not a comment contains a title argument THEN this title is used`() {
+        val expectedTitle = "expectedTitle"
+        val defaultTitle = "defaultTitle"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_TITLE)).thenReturn(StringEscapeUtils.escapeHtml4(expectedTitle))
+        val title = notificationHelper.getNotificationTitle(mockedBundle, PUSH_TYPE_NOT_A_COMMENT, defaultTitle)
+        assertEquals(expectedTitle, title)
+    }
+
+    @Test
+    fun `WHEN a PN that is not a comment does not contain a title argument THEN the default title is used`() {
+        val defaultTitle = "defaultTitle"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_TITLE)).thenReturn(null)
+        val title = notificationHelper.getNotificationTitle(mockedBundle, PUSH_TYPE_NOT_A_COMMENT, defaultTitle)
+        assertEquals(defaultTitle, title)
+    }
+
+    @Test
+    fun `WHEN a PN that is not a comment contains a message argument THEN the message is used`() {
+        val expectedMessage = "expectedMessage"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_MSG)).thenReturn(StringEscapeUtils.escapeHtml4(expectedMessage))
+        val message = notificationHelper.getNotificationMessage(mockedBundle, PUSH_TYPE_NOT_A_COMMENT)
+        assertEquals(expectedMessage, message)
+    }
+
+    @Test
+    fun `WHEN a PN that is not a comment does not contain a message argument THEN an empty message is used`() {
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_MSG)).thenReturn(null)
+        val message = notificationHelper.getNotificationMessage(mockedBundle, PUSH_TYPE_NOT_A_COMMENT)
+        assertEquals("", message)
+    }
+
+    @Test
+    fun `WHEN a PN that is a comment has a comment payload THEN the comment is used as a message`() {
+        val expectedMessage = "expectedMessage"
+        val mockedNote = mock<Note>()
+        val noteId = "noteId"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_NOTE_ID)).thenReturn(noteId)
+        whenever(notificationsUtilsWrapper.getNoteById(noteId)).thenReturn(mockedNote)
+        whenever(mockedNote.commentSubject).thenReturn(expectedMessage)
+        val message = notificationHelper.getNotificationMessage(mockedBundle, PUSH_TYPE_COMMENT)
+        assertEquals(expectedMessage, message)
+    }
+
+    @Test
+    fun `WHEN a PN that is a comment does not have a comment payload and contains a message THEN the later is used`() {
+        val expectedMessage = "expectedMessage"
+        val mockedNote = mock<Note>()
+        val noteId = "noteId"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_NOTE_ID)).thenReturn(noteId)
+        whenever(notificationsUtilsWrapper.getNoteById(noteId)).thenReturn(mockedNote)
+        whenever(mockedNote.commentSubject).thenReturn(null)
+        whenever(mockedBundle.getString(PUSH_ARG_MSG)).thenReturn(StringEscapeUtils.escapeHtml4(expectedMessage))
+        val message = notificationHelper.getNotificationMessage(mockedBundle, PUSH_TYPE_COMMENT)
+        assertEquals(expectedMessage, message)
+    }
+
+    @Test
+    fun `WHEN a PN that is a comment does not have a comment payload or a message THEN an empty message is used`() {
+        val mockedNote = mock<Note>()
+        val noteId = "noteId"
+        val mockedBundle = mock<Bundle>()
+        whenever(mockedBundle.getString(PUSH_ARG_NOTE_ID)).thenReturn(noteId)
+        whenever(notificationsUtilsWrapper.getNoteById(noteId)).thenReturn(mockedNote)
+        whenever(mockedNote.commentSubject).thenReturn(null)
+        whenever(mockedBundle.getString(PUSH_ARG_MSG)).thenReturn(null)
+        val message = notificationHelper.getNotificationMessage(mockedBundle, PUSH_TYPE_COMMENT)
+        assertEquals("", message)
+    }
+}