Skip to content

Add UnifiedPush support for push notifications#6621

Draft
sk7n4k3d wants to merge 5 commits intohome-assistant:mainfrom
sk7n4k3d:unifiedpush-implementation
Draft

Add UnifiedPush support for push notifications#6621
sk7n4k3d wants to merge 5 commits intohome-assistant:mainfrom
sk7n4k3d:unifiedpush-implementation

Conversation

@sk7n4k3d
Copy link
Copy Markdown

@sk7n4k3d sk7n4k3d commented Mar 23, 2026

Summary

  • Implements UnifiedPush as an alternative push notification provider, allowing notifications via a distributor app (e.g. ntfy) without FCM
  • Adds UnifiedPushProvider, UnifiedPushManager, UnifiedPushReceiver, and UnifiedPushWorker
  • Extends the Push provider settings to list installed UnifiedPush distributors with their app names
  • Adds IgnoreViolationRule for UnifiedPush connector library StrictMode violations (following the existing pattern for third-party libs)
  • Handles protobuf-java/javalite conflict via exclusion
  • Adds UnifiedPush guard in FcmPushProvider.isActive() and FCM onNewToken()

Depends on #6619 (PushProvider abstraction). This PR adds the UnifiedPush-specific implementation on top.

Testing

Tested on Pixel 9 Pro Fold (Android 16, GrapheneOS) with ntfy as UnifiedPush distributor:

  • Switch between WebSocket and UnifiedPush in both directions without app restart
  • Notifications received on both providers in foreground, background, and with app killed
  • UnifiedPush delivers via ntfy independently when app is killed
  • Auto-fallback to WebSocket when ntfy topic is deleted
  • No StrictMode/FailFast crashes
  • 11 unit tests for UnifiedPush message parsing

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Copilot AI review requested due to automatic review settings March 23, 2026 21:39
Copy link
Copy Markdown

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aaronstealth

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@home-assistant home-assistant bot marked this pull request as draft March 23, 2026 21:40
@home-assistant
Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@sk7n4k3d sk7n4k3d marked this pull request as ready for review March 23, 2026 21:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements UnifiedPush support as an alternative push notification provider for the Home Assistant Android app. UnifiedPush allows users to receive push notifications via a distributor app (like ntfy) without relying on Google's FCM infrastructure.

Changes:

  • Implements a generic PushProvider abstraction layer with PushProviderManager for managing multiple push notification providers
  • Adds UnifiedPushProvider, UnifiedPushManager, UnifiedPushReceiver, and UnifiedPushWorker for UnifiedPush integration
  • Extends DeviceRegistration with pushUrl and pushEncrypt fields to support multiple push provider types
  • Implements FcmPushProvider and WebSocketPushProvider to work with the new abstraction
  • Adds settings UI (ListPreference) for users to select their preferred push provider
  • Includes comprehensive unit tests (186 lines in UnifiedPushMessageParsingTest, 204 lines in PushProviderManagerTest)

Reviewed changes

Copilot reviewed 33 out of 36 changed files in this pull request and generated no comments.

Show a summary per file
File Description
gradle/libs.versions.toml Adds unifiedpush-connector 3.0.9 dependency; includes several unexplained version downgrades
common/src/main/kotlin/push/ New PushProvider interface and PushProviderManager for managing providers
common/src/main/kotlin/data/ Extends DeviceRegistration and related classes with push URL and encryption fields
common/src/test/kotlin/push/ Comprehensive unit tests for push provider functionality
app/src/main/kotlin/push/ Implements PushProvider variants (FCM, WebSocket, UnifiedPush)
app/src/main/kotlin/unifiedpush/ UnifiedPush-specific implementation (Manager, Worker, Receiver)
app/src/main/kotlin/settings/ Settings integration for push provider selection
app/src/main/AndroidManifest.xml Registers UnifiedPushReceiver with UnifiedPush intent filters
app/lint-baseline.xml Acknowledges ExportedReceiver lint warning for UnifiedPushReceiver
build-logic/convention/ Adds unifiedpush-connector with protobuf-java exclusion to handle conflicts

@sk7n4k3d sk7n4k3d force-pushed the unifiedpush-implementation branch from eefa371 to 4caaaea Compare March 23, 2026 22:47
Copy link
Copy Markdown

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aaronstealth

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@home-assistant home-assistant bot marked this pull request as draft March 23, 2026 22:47
sk7n4k3d and others added 2 commits March 24, 2026 13:33
Introduce a PushProvider interface that abstracts push notification
delivery, allowing the user to choose between available providers
(FCM, WebSocket) via a new "Push provider" setting under Notifications.

This lays the groundwork for additional providers (e.g. UnifiedPush)
without coupling the abstraction to any specific implementation.

- Add PushProvider interface with name, isAvailable, isActive, register,
  unregister, and requiresPersistentConnection
- Add PushProviderManager for provider selection and server registration
- Add FcmPushProvider (full flavor) and WebSocketPushProvider
- Add Dagger multibinding modules for both flavors
- Add "Push provider" ListPreference in Notifications settings
- Add WebsocketManager.restart() for instant provider switching
- Extend DeviceRegistration with pushUrl and pushEncrypt fields
- Add 28 unit tests for PushProvider and PushProviderManager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove suspend from WebsocketManager.restart() (no suspending calls)
- Remove unused context parameter from WebSocketPushProvider
- Clear server-side push config when switching to WebSocket
- Rethrow CancellationException in PushProviderManager and FcmPushProvider
- Derive initial push provider from available providers instead of hardcoded default
- Stabilize getAllProviders() ordering with sortedBy name
- Fix PushProvider KDoc to reflect user-selection model
- Wire handlePushProviderChange to actually call selectAndRegister
- Remove dead getString/putString cases for notification_push_provider
- Move push provider label resolution to Fragment using string resources

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sk7n4k3d sk7n4k3d force-pushed the unifiedpush-implementation branch from 4caaaea to 55aa73c Compare March 24, 2026 12:34
- Persist selectedPushProvider via PrefsRepository instead of in-memory
  variable to survive process death
- Trigger server-side registration update when push provider changes
  by calling updateServerRegistration after selectAndRegister
- Add explanatory comment for preferenceDataStore = null in
  SettingsFragment push provider preference
- Replace hardcoded "WebSocket" fallback with WebSocketPushProvider.NAME
  constant reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sk7n4k3d sk7n4k3d force-pushed the unifiedpush-implementation branch from 55aa73c to b18ace5 Compare March 24, 2026 13:11
@sk7n4k3d sk7n4k3d marked this pull request as ready for review March 24, 2026 13:13
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Argument should be on a separate line (unless all arguments can fit a single line)

Argument should be on a separate line (unless all arguments can fit a single line)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Missing newline before ")"

Missing newline before ")"
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Missing newline before ")"

Missing newline before ")"
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Exceeded max line length (120)

Exceeded max line length (120)
presenter.handlePushProviderChange(value)
}
if (value == "WebSocket") {
Toast.makeText(requireContext(), commonR.string.push_provider_websocket_enabled, Toast.LENGTH_SHORT).show()

Check failure

Code scanning / ktlint

Exceeded max line length (120)

Exceeded max line length (120)
@TimoPtr TimoPtr marked this pull request as draft March 25, 2026 15:37
@TimoPtr
Copy link
Copy Markdown
Member

TimoPtr commented Mar 25, 2026

Let's first review the first PR.

sk7n4k3d and others added 2 commits March 25, 2026 16:54
…ment

- Fix import ordering in SettingsFragment.kt and SettingsPresenterImpl.kt
- Fix argument wrapping for Toast.makeText call in SettingsFragment.kt
- Fix constructor parameter formatting in FcmPushProvider.kt
- Clarify PushProvider KDoc: explicitly note user-configurable selection
  with no automatic "best provider" logic
Implement UnifiedPush as an alternative push notification provider,
allowing users to receive notifications via a distributor app (e.g. ntfy)
without relying on Google's FCM infrastructure.

- Add UnifiedPushProvider with distributor-aware registration
- Add UnifiedPushManager for registration lifecycle and retry logic
- Add UnifiedPushReceiver for message and endpoint handling
- Add UnifiedPushWorker for background retry with backoff
- Add UnifiedPush distributor selection in Push provider settings
- Add IgnoreViolationRule for UnifiedPush connector StrictMode violations
- Add protobuf-java exclusion to avoid conflict with protobuf-javalite
- Add UnifiedPush guard in FcmPushProvider.isActive() and FCM onNewToken
- Extend MessagingManager with Map<String, Any> message handler
- Add 11 unit tests for UnifiedPush message parsing
- Add unifiedpush-connector 3.0.9 dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sk7n4k3d sk7n4k3d force-pushed the unifiedpush-implementation branch from b18ace5 to 01ffbe6 Compare March 25, 2026 15:59
@sk7n4k3d
Copy link
Copy Markdown
Author

Makes sense! I've rebased this on top of the updated #6619 and ktlint passes now. Ready whenever #6619 is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants