Add UnifiedPush support for push notifications#6621
Add UnifiedPush support for push notifications#6621sk7n4k3d wants to merge 5 commits intohome-assistant:mainfrom
Conversation
There was a problem hiding this comment.
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!
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
There was a problem hiding this comment.
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
PushProviderabstraction layer withPushProviderManagerfor managing multiple push notification providers - Adds
UnifiedPushProvider,UnifiedPushManager,UnifiedPushReceiver, andUnifiedPushWorkerfor UnifiedPush integration - Extends
DeviceRegistrationwithpushUrlandpushEncryptfields to support multiple push provider types - Implements
FcmPushProviderandWebSocketPushProviderto 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 |
eefa371 to
4caaaea
Compare
There was a problem hiding this comment.
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!
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>
4caaaea to
55aa73c
Compare
- 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>
55aa73c to
b18ace5
Compare
| 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)
| 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)
| 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)
| 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)
| 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)
| 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)
| 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 ")"
| 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 ")"
| 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)
| 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)
|
Let's first review the first PR. |
…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>
b18ace5 to
01ffbe6
Compare
Summary
UnifiedPushProvider,UnifiedPushManager,UnifiedPushReceiver, andUnifiedPushWorkerIgnoreViolationRulefor UnifiedPush connector library StrictMode violations (following the existing pattern for third-party libs)FcmPushProvider.isActive()and FCMonNewToken()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:
Type of change