Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only fetch and display a given notification once #3626

Merged
merged 14 commits into from
May 13, 2023
Merged

Only fetch and display a given notification once #3626

merged 14 commits into from
May 13, 2023

Conversation

nikclayton
Copy link
Contributor

@nikclayton nikclayton commented May 5, 2023

When fetching:

  • Maintain a marker with the position of the newest fetched notification
  • Use the marker to determine which notifications to fetch
  • Fetch notifications with min_id to ensure that none are lost
  • Update the marker as necessary
  • Perform a one-time immediate fetch of notifications on startup

When creating notifications:

  • Identify each notification with tag=${MastodonNotificationId}, id=${account.id}
  • Remove activeNotifications field, it's no longer necessary
  • Use the tag/id tuple to reliably identify existing notifications and avoid creating duplicates
  • Cancelling notifications for an account must iterate over all the notifications, and individually remove the notifications that exist for that account.
  • Limit notifications to a maximum of 40 (excluding summary notifications)
  • Remove notifications (oldest first) to get under this limit
  • Rate limit notification creation to 1 per second, so the OS won't drop them

Adjust the summary notification:

  • Ensure the summary notification and the child notifications have the same group key
  • Dismiss the summary notification if there is only one child notification

NotificationClearBroadcastReceiver is no longer needed, so remove it, and the need for deletePendingIntent.

Fixes #3625, #3539

- Maintain a marker with the position of the newest fetched notification
- Use the marker to determine which notifications to fetch
- Fetch notifications with min_id to ensure that none are lost
- Update the marker as necessary

Fixes #3625
Nik Clayton added 2 commits May 7, 2023 15:12
This was tracking (as a JSON string) an array of currently shown Android notifications. However, this could get out of sync with the actual display, and there was no mechanism to get it back in sync.

Replace it by identifying each Android notification with a tuple of `(tag = mastodon-notification-id, id = tusky-account-id)`. Use this to reliably find existing notifications in NotificationHelper.make().

Cancelling notifications for an account must iterate over all the notifications, and individually remove the notifications that exist for that account.

Adjust the summary notification:
- Ensure the summary notification and the child notifications have the same group key
- Dismiss the summary notification if there is only one child notification

NotificationClearBroadcastReceiver is no longer needed, so remove it, and the need for deletePendingIntent.
Periodic work requests are supposed to start running soon after being enqueued. In practice that may be multiple minutes after the request is enqueued, so create and enqueue an expedited one-time request to get new notifications immediately.
@nikclayton nikclayton changed the title Only fetch a given notification once Only fetch and display a given notification once May 7, 2023
@nikclayton nikclayton requested a review from connyduck May 7, 2023 13:26
@nikclayton
Copy link
Contributor Author

Worth noting -- using the marker API means that if the user has multiple devices notifications will be distributed amongst them somewhat randomly (from the perspective of the user)

E.g., if the user has device A and device B, A is checking for notifications every 15 minutes on the hour, and B is checking for notifications every 15 minutes starting at 5 minutes past the hour and the following notifications come in:

N1 @ 00:00
N2 @ 00:01
N3 @ 00:07
N4 @ 00:16
N5 @ 00:21

then device A will receive notifications N1, N2, N4, and device B will receive notifications N3 and N5.

Note that device A and device B don't have to be two different devices running Tusky. One of them could be Tusky, the other could be the user signed in to the web app (e.g., I just checked on mastodon.social, and opening the web app makes GET and POST requests to the markers API for notifications and the home timeline).

We might want to put this in a preference in v23

# Conflicts:
#	app/schemas/com.keylesspalace.tusky.db.AppDatabase/49.json
#	app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
NotificationFetcher.fetchAndShow():

- Limit notifications to a maximum of 40 (excluding summary notifications)
- Remove notifications (oldest first) to get under this limit
- Take responsibility for calling NotificationManager.notify() for each regular notification
- Rate limit notification creation to 1 per second, so the OS won't drop them

NotificationHelper:
- Update "extras" keys to use the application ID so they can't clash with other keys (per docs)

.make():
- Just create the notification, don't notify about it as well
- Include the notification sending account name and type as extras, for use in the summary

.updateSummaryNotifications():
- Update *all* summary notifications for an account.
- This ensures that they are created, modified, or deleted based on the state of *all* active notifications, not just the ones most recently received.

General:

Use more specific dependencies where possible. E.g., if a function needs a NotificationManager, don't pass in a Context and assume it will use that, pass in the NotificationManager it needs.

Adjust other call sites and tests as necessary.
@nikclayton
Copy link
Contributor Author

Looking at this code some more I think there's another problem.

If I'm reading it properly only one summary notification can be generated. That summary notification will be assigned to whichever notification channel (follows, boosts, mentions, etc...) was assigned to the last notification seen.

I think that's a problem, because:

  1. The user may have muted that notification channel. I assume that will mute the summary notification as well. But the summary notification might be summarising different types of notification (follow, boost, etc), so now the entire summary is muted when only the summary for a specific channel should be muted.
  2. The Android documentation is silent on what happens if notifications in different channels are part of the same summary group, but I suspect that's an edge case that's not handled well.

Also, with one summary the user can choose to dismiss all notifications with one swipe, which might not be appropriate.

So I think this needs to be modified to create N summary notifications, one per notification channel that contains 2+ notifications.

  • The summary notification will be muted if the channel is muted
  • The user can easily dismiss all notifications of a particular type (e.g., boosts) without affecting the other notifications

I'll work on that now.

@nikclayton
Copy link
Contributor Author

OK, I think that's fixed. The new code:

  1. Creates a summary notification for each notification group (follow, reblog, mention, etc). This ensures that if one channel is muted the summary notifications for other channels are not affected
  2. Updates those summary notifications every time new notifications come in

It also rate limits the calls to notify(), if you do too many too quickly Android will silently drop the notification. This may be the cause of some "missing notification" reports in the past.

Android also has a limit on the number of active notifications, and will not create new ones if the app is at that limit. To work around that, this code will also delete older Android notifications if it needs space to show newer ones.

Note that in this case you can't, e.g., update the summary notification and say something like "40 notifications shown, 20 hidden", because on API 24 and above Android manages the text of the summary notification for you, overriding whatever text the app uses for it.

Possible bug, but I'm not sure I'm worried about it for v22:

  • If the user swipes a notification away the summary notification for that group will not be modified.

@nikclayton nikclayton requested a review from connyduck May 10, 2023 07:19
@nikclayton
Copy link
Contributor Author

While I'm here, there's at least one more bug which this doesn't fix yet.

fetchNotifications (fetchNewNotifications in this PR) makes one call to get new notifications (mastodonApi.notificationsWithAuth).

That will return N new notifications, starting with the oldest new one, up to limit. limit is not set in this code, and is documented as being default=15 max=30 at https://docs.joinmastodon.org/methods/notifications/#get.

If we can trust these documented defaults this means that in any given notification fetch (i.e., approx every 15 minutes) Tusky will get the batch of next-newest 15 notifications and display them. If the user has more notifications than that they will have to wait another ~ 15 minutes until they are fetched.

That's probably also a source of user complaints about missing notifications.

Fix is to either:

  1. Use the NotificationsRepository and fetch the notifications that way, taking advantage of its existing code for paging up (prepending) the list of notifications. Not sure how well Pager3 is suited to that though.
  2. Write some additional code here that repeatedly fetches new notifications until there are no new ones.

@nikclayton nikclayton merged commit 81b15e7 into tuskyapp:develop May 13, 2023
@nikclayton nikclayton deleted the 3625-duplicate-notifications branch May 13, 2023 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Do not show notifications if already seen
3 participants