Repository navigation
feat(android): send ongoing notification actions, replies, taps and dismissals to JavaScript - #365
Open
xdimota7x wants to merge 4 commits into
Conversation
Adds a persisted native event queue with retention, a size cap and coalescing, exposed through popEvents/restoreEvents/onEventsAvailable, and a JS dispatcher behind addVoltraListener for the six widget lifecycle event types.
The Dynamic Widget receiver now records widgetAdded, widgetResized, widgetRemoved, widgetRestored, widgetEnabled and widgetDisabled through a placement registry, and getActiveWidgets() reports each placement's launcher sizes.
Adds ADR 0014, a website page for the lifecycle events and the sizes field, and the version plan.
…ismissals to JavaScript An `AndroidOngoingNotification.Action` can now take an `actionId` instead of a `deepLinkUrl`. A press records an `interaction` event in the Android event channel (ADR 0014) without opening the app; when the app is not in the foreground, a short headless service runs its JavaScript until the listeners finish. Event actions can carry a `payload` and a `remoteInput` for direct reply; both action forms accept `authenticationRequired` and `semanticAction`. Dismissals and content taps are reported as `notificationDismissed` and `notificationTapped`. Implements callstackincubator#316 on top of the channel from callstackincubator#361; decisions recorded in ADR 0015.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this?
Android ongoing notification actions can now run app code instead of only opening a deep link. An action can set an
actionId, with an optional stringpayloadand an inline reply field (remoteInput, with optionalchoices). Pressing it sends aninteractionevent to JavaScript, even when the app is in the background or its process has been killed. Apps also receivenotificationTappedwhen the user taps the notification body, andnotificationDismissedwhen the user swipes it away. Actions can also setauthenticationRequiredandsemanticAction.Closes #316. This PR builds on the Android event channel from #361 and should be reviewed and merged after it.
How does it work?
AndroidOngoingNotification.Actiontakes eitherdeepLinkUrl(unchanged) oractionId, never both. The renderer rejects invalid combinations, and existing deep-link payloads serialise exactly as before. On Android 12+ a notification receiver may not start an Activity, so one press can either open the app or send an event, not both.An event action uses a broadcast PendingIntent that targets
VoltraOngoingNotificationActionReceiver. The receiver:interactionevent in feat(android-client): surface Dynamic Widget lifecycle events to the app #361's event channel, withidentifier,payload,notificationId,revisionand an optionalreply: { key, text, source }.The channel never starts JavaScript itself, so this PR adds that step. When the app is not in the foreground, the receiver starts
VoltraHeadlessEventService, a short foreground service that runs theVoltraEventsheadless task. The task pulls the queued events and waits for the promises the listeners return, so the app can update the notification before the process can be frozen. When the app is in the foreground, nothing is started; the app pulls the event as usual.Taps and dismissals are recorded in the same channel and never start JavaScript. A tap is read from the launch intent on a cold start and from
onNewIntenton a warm start, and is reported once.Apps listen with
addVoltraListener('interaction' | 'notificationTapped' | 'notificationDismissed', …)at module scope. On iOS these three event types log a warning and do nothing. Events wait up to 15 minutes for a listener, the channel's default.Each record now keeps its last payload and a revision. The revision increases on every update, so an app can ignore a press on buttons from an older version of the notification.
stopOngoingNotificationandendAllOngoingNotificationscancel the action intents, so buttons left in the shade after a stop do nothing. The Expo plugin registers the receivers and the service, and adds theFOREGROUND_SERVICEandWAKE_LOCKpermissions underenableNotifications.ADR 0015 records the design, including where it departs from #316:
toMap/fromMapare not needed.Why is this useful?
Apps can handle actions like "Pause", "Add tip" or "Stop navigation" from the notification shade without bringing the app to the foreground, even after Android has killed the process. Before this, every action had to open the app through a deep link, and the app never learned that its notification was tapped or dismissed. This brings Android ongoing notifications closer to the
interactionevents iOS Live Activities already have.