Adds iOS Live Activities support#4444
Conversation
There was a problem hiding this comment.
Pull request overview
Adds ActivityKit-based Live Activities support to the Home Assistant iOS app, enabling notifications to start/update/end a Lock Screen/Dynamic Island Live Activity via homeassistant.command or homeassistant.live_activity payload fields, plus UI/settings and device registration support.
Changes:
- Adds a Live Activity attributes model and an actor-based registry to manage activity lifecycle, push tokens, and dismissal reporting.
- Extends notification command routing/handlers to start/update/end Live Activities (including
clear_notification+tagdismissal). - Adds widget extension
ActivityConfigurationUI, settings UI, localization strings, and new unit tests for routing/handlers.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Shared/LiveActivity/NotificationsCommandManagerLiveActivityTests.swift | Adds routing tests for live_activity, end_live_activity, and flag-based routing. |
| Tests/Shared/LiveActivity/MockLiveActivityRegistry.swift | Provides a registry test double for handler/manager tests. |
| Tests/Shared/LiveActivity/HandlerLiveActivityTests.swift | Adds validation/parsing/guard/dismissal-policy tests for the new handlers. |
| Sources/Shared/Settings/SettingsStore.swift | Persists a “privacy disclosure seen” flag for Live Activities. |
| Sources/Shared/Resources/Swiftgen/Strings.swift | Adds generated localization accessors for Live Activities settings strings. |
| Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift | Registers Live Activity commands and adds live_activity: true routing + clear_notification Live Activity end behavior. |
| Sources/Shared/Notifications/NotificationCommands/HandlerLiveActivity.swift | Implements start/update and end handlers, including payload parsing and validation. |
| Sources/Shared/LiveActivity/LiveActivityRegistry.swift | Adds an actor to manage activities, observe token/lifecycle streams, and report webhooks. |
| Sources/Shared/LiveActivity/HALiveActivityAttributes.swift | Defines ActivityAttributes / ContentState wire model for Live Activities. |
| Sources/Shared/Environment/Environment.swift | Adds apnsEnvironment helper and a lazily created liveActivityRegistry environment dependency. |
| Sources/Shared/API/HAAPI.swift | Extends registration payload with Live Activities capability and token fields. |
| Sources/Extensions/Widgets/Widgets.swift | Registers the Live Activity widget configuration in widget bundles with iOS 16.2 gating. |
| Sources/Extensions/Widgets/LiveActivity/HALockScreenView.swift | Implements the Lock Screen/StandBy UI for the activity. |
| Sources/Extensions/Widgets/LiveActivity/HALiveActivityConfiguration.swift | Adds ActivityConfiguration wrapper for the Live Activity widget. |
| Sources/Extensions/Widgets/LiveActivity/HADynamicIslandView.swift | Implements Dynamic Island compact/minimal/expanded views. |
| Sources/App/Settings/Settings/SettingsItem.swift | Adds Live Activities to Settings navigation and gates it behind iOS 16.2. |
| Sources/App/Settings/LiveActivity/LiveActivitySettingsView.swift | Adds a settings screen for status, active activities list, privacy text, and debug scenarios. |
| Sources/App/Resources/en.lproj/Localizable.strings | Adds English strings for Live Activities settings UI. |
| Sources/App/Resources/Info.plist | Enables Live Activities + Frequent Updates support via Info.plist keys. |
| Sources/App/AppDelegate.swift | Reattaches surviving activities at launch and starts observing push-to-start tokens (iOS 17.2+). |
| HomeAssistant.xcodeproj/project.pbxproj | Adds new source files/groups and adjusts build settings (including a Widgets debug signing team). |
Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift
Show resolved
Hide resolved
Sources/App/Settings/LiveActivity/LiveActivitySettingsView.swift
Outdated
Show resolved
Hide resolved
Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift
Show resolved
Hide resolved
Add support for iOS Live Activities in the mobile_app integration: - Add `supports_live_activities`, `supports_live_activities_frequent_updates`, `live_activity_push_to_start_token`, and `live_activity_push_to_start_apns_environment` fields to SCHEMA_APP_DATA for explicit validation during device registration - Add `update_live_activity_token` webhook handler: stores per-activity APNs push tokens reported by the iOS companion app when a Live Activity is created locally via ActivityKit - Add `live_activity_dismissed` webhook handler: cleans up stored tokens when a Live Activity ends on the device - Both handlers fire bus events so automations can react to activity lifecycle - Add `supports_live_activities()` utility helper - Add 4 tests covering token storage, default environment, dismissal cleanup, and nonexistent tag dismissal for: home-assistant/mobile-apps-fcm-push#278 for: home-assistant/iOS#4444 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
tip for lint you can use |
|
Can you make a screen recording showing the whole flow? Showing HA UI sending the push and iPhone handling it |
So I don't have a paid Apple dev account so apparently the only way I could get it to compile to an actual device (to try to get push notifs) was to rip a bunch of entitlements out to compile for my physical device. For the emulator, I wasn't sure how to do a full-end-to-end test with the four repo changes altogether and run that locally. Theoretically it seemed like a possibility to do a local push to the emulated device but didn't get to that point. Mainly why I created the debug options in the screenshot section showcasing all of the possibilities with the YAML at the moment. (This is in App Companion Settings -> Live Activity -> DEBUG options) I don't mind putting some more stuff together, lemme know any specifics you would like to see and I can try. There's also more screenshots in the documentation repo here: home-assistant/companion.home-assistant#1303 |
|
Nowadays you can already test push notification on simulator as well from what I can remember, check this https://www.tiagohenriques.dev/blog/testing-push-notifications-ios-simulators |
|
Thanks, I'll look at this and see if I can produce some more examples for you / video on top of the previous gif |
For the local WebSocket path, the simulator is currently connecting through Nabu Casa (cloud relay), so notifications route through FCM rather than the local WebSocket channel. I'd need to be on the same local network as the HA instance for WebSocket-based delivery to work — In the meantime, I've added two screen recordings demonstrating start/update/end with various payload configurations on the simulator. The debug section in Settings also exercises all the UI states. As well as correcting the linting issues Settings.Area.mp4Debug.sample.mp4 |
You have that already right? Because you opened a PR for core as well, so you can use that instance you used to test core to connect to the iOS App |
Sources/Extensions/Widgets/LiveActivity/HADynamicIslandView.swift
Outdated
Show resolved
Hide resolved
Sources/Extensions/Widgets/LiveActivity/HADynamicIslandView.swift
Outdated
Show resolved
Hide resolved
Sources/Extensions/Widgets/LiveActivity/HALiveActivityConfiguration.swift
Outdated
Show resolved
Hide resolved
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4444 +/- ##
=======================================
Coverage ? 42.24%
=======================================
Files ? 271
Lines ? 16115
Branches ? 0
=======================================
Hits ? 6808
Misses ? 9307
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
In your example we have 2 different tags, one for android and one for iOS: Should we use the same as android has so the user does not need 2 different approaches based on platform? Are there more differences that we could merge into a single approach? |
Add support for iOS Live Activities in the mobile_app integration: - Add `supports_live_activities`, `supports_live_activities_frequent_updates`, `live_activity_push_to_start_token`, and `live_activity_push_to_start_apns_environment` fields to SCHEMA_APP_DATA for explicit validation during device registration - Add `update_live_activity_token` webhook handler: stores per-activity APNs push tokens reported by the iOS companion app when a Live Activity is created locally via ActivityKit - Add `live_activity_dismissed` webhook handler: cleans up stored tokens when a Live Activity ends on the device - Both handlers fire bus events so automations can react to activity lifecycle - Add `supports_live_activities()` utility helper - Add 4 tests covering token storage, default environment, dismissal cleanup, and nonexistent tag dismissal for: home-assistant/mobile-apps-fcm-push#278 for: home-assistant/iOS#4444 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8af7679 to
c4ad13d
Compare
Correct yeah, I haven't stood up core before locally from the repo will take a stab and see if I can ping HASS Core directly from the localhost from the iPhone sim. Was just using my personal instance to try at first.
Great point actually. I can change this to just use the pre-existing Unless you think it would be handy to have the following example:
But overall, the field names inside data (tag, progress, chronometer, etc.) are already unified across both platforms — it's only the opt-in trigger that's separate. Responded to all comments and questions, updated suggested code changes and integrated requested tests. |
…ivities Unifies the iOS and Android notification data field: live_update: true now triggers a Live Activity on iOS, matching the field Android already uses for Live Updates. A single YAML automation now targets both platforms with no platform-specific keys. Internal command strings, webhook types, and keychain keys are unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No idea, not sure. Sounds good.
No problem, has grown into a large PR here lol
Let me know if we want to cutoff the feature for iOS 17+ rather than 16.2+ and I can do that too. |
|
I see many checks for iOS 16.2 but since we can only start activities from server side (Home Assistant) on 17.2+, doesnt it make more sense to support starting on 17.2? EDIT: I see my comment overlapped yours haha sorry, yes let's move for 17 |
Replaces all iOS 16.2 availability checks with iOS 17.2, flattens the nested 16.2/17.2 blocks in AppDelegate and HAAPI into a single 17.2 check, and removes now-redundant @available(iOS 17.2, *) method annotations from the protocol and actor (the outer type annotation is sufficient). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Are you on discord? If so can you reach out to me, I want to have a quick chat about the full picture and If I understood your implementation correctly (bgoncal2) |
Yes I am, I'll reach out! |
Sources/Shared/Notifications/NotificationCommands/HandlerLiveActivity.swift
Outdated
Show resolved
Hide resolved
Tests/Shared/LiveActivity/NotificationsCommandManagerLiveActivityTests.swift
Show resolved
Hide resolved
…ard, live_update test key - isValidTag: replace CharacterSet.alphanumerics (Unicode-inclusive) with explicit ASCII character set to match the stated [a-zA-Z0-9_-] contract - WidgetsBundle17: wrap HALiveActivityConfiguration() in #available(17.2) guard, matching WidgetsBundleLegacy — avoids availability error on iOS 17.0/17.1 - Tests: change live_activity flag to live_update to match production routing (NotificationsCommandManager routes on live_update, not live_activity) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cking Per bgoncal review feedback: remove the Any? backing store workaround and the @available annotation on the stored property. Move @available(iOS 17.2, *) to individual protocol methods so LiveActivityRegistryProtocol can be referenced without an availability guard, then store it as LiveActivityRegistryProtocol? and return nil at runtime when on iOS < 17.2. Update all call sites to optional-chain (?.); AppDelegate uses guard-let since it already holds a non-nil reference after pre-warming inside #available(17.2). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restores the original single-line form to keep the diff focused on Live Activities changes, per bgoncal review feedback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…me-assistant#4453) This property was added in an earlier commit but main removed the TestFlight gate for mTLS entirely (PR home-assistant#4453). Drop it to avoid a merge conflict and align with the direction main took. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iveActivities BetaLabel was removed in main (home-assistant#4453). Resolve conflict by keeping LabsLabel() for both .kiosk and .liveActivities entries in the settings list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per bgoncal feedback: the backing store + computed property is redundant. A single lazy var handles initialization on first access, returns nil on iOS < 17.2, and is still reassignable for test injection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aligns with HA core change: the generic 'tag' key in the dismissed webhook payload is renamed to 'live_activity_tag' to avoid ambiguity with the notification tag field used elsewhere in mobile_app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bgoncal
left a comment
There was a problem hiding this comment.
I think we can merge this one, let's just add a Current.isTestFlight check in the settings entry since we still need to validate that the integration with the FCM and Core PRs will work and not get this shipped by mistake.
Sounds good, I will add this in. Definitely, I would hate to get anything merged in that doesn't work with the full end to end obviously outside of just the websocket working. In recent news, I did pay for the Apple Developer account so let me know if there's something I could potentially try on my end to test the FCM. Especially considering the minor changes I did yesterday for the |
|
You would need to create your own firebase project, link it in a local core installation and modify the app In FCM repo there are more instructions if you wanna give a try |
SettingsItem.swift:155-158 and :165-167 already have Current.isTestFlight checks guarding the Live Activities entry. But let me know if this isn't correct If this is taken care of not sure if everyone is satisfied to give this a merge! |

Summary
Adds iOS Live Activities support, letting Home Assistant automations push real-time state to the Lock Screen — washing machine countdowns, EV charging progress, delivery tracking, alarm states, or anything time-sensitive that benefits from glanceable visibility without unlocking the phone.
When an automation sends a notification with
live_update: truein the data payload, the companion app starts a Live Activity instead of (or in addition to) a standard notification banner. Subsequent pushes with the sametagupdate it in-place silently.clear_notification+tagends it.Field names (
tag,title,message,progress,progress_max,chronometer,when,when_relative,notification_icon,notification_icon_color) are intentionally shared with Android's Live Notifications API. Both platforms use the samelive_update: truetrigger — a single YAML block targets iOS 17.2+ and Android 16+ with no platform-specific keys.New files:
Sources/Shared/LiveActivity/HALiveActivityAttributes.swift— theActivityAttributestype. Field names match the Android payload spec. Struct name andCodingKeysare wire-format frozen — APNs push-to-start payloads reference the Swift type name directly.Sources/Shared/LiveActivity/LiveActivityRegistry.swift— SwiftactormanagingActivity<HALiveActivityAttributes>lifecycle. Uses a reservation pattern to prevent duplicate activities when two pushes with the sametagarrive simultaneously.Sources/Shared/Notifications/NotificationCommands/HandlerLiveActivity.swift— start/update and endNotificationCommandHandlerimplementations, guarded against thePushProviderextension process where ActivityKit is unavailable.Sources/Extensions/Widgets/LiveActivity/—ActivityConfigurationwrapper, Lock Screen / StandBy view, and compact / minimal / expanded Dynamic Island views.Sources/App/Settings/LiveActivity/LiveActivitySettingsView.swift— activity status, active list, privacy disclosure, and 11 debug scenarios for pre-server-side testing.Modified files:
Widgets.swift— registersHALiveActivityConfigurationin all threeWidgetBundlevariants behind#available(iOSApplicationExtension 17.2, *)NotificationsCommandManager.swift— registers new handlers;HandlerClearNotificationnow also ends a matching Live Activity whentagis presentHAAPI.swift— addssupports_live_activities,supports_live_activities_frequent_updates,live_activity_push_to_start_token,live_activity_push_to_start_apns_environmentto registration payload under a single#available(iOS 17.2, *)checkAppDelegate.swift— reattaches surviving activities at launch and starts observing push-to-start tokens under a single#available(iOS 17.2, *)checkInfo.plist—NSSupportsLiveActivities+NSSupportsLiveActivitiesFrequentUpdatesSettingsItem.swift/SettingsView.swift— Live Activities settings row is gated behindCurrent.isTestFlightand shows aBetaLabelbadgeTests:
Screenshots
Link to pull request in Documentation repository
Documentation: home-assistant/companion.home-assistant#1303
Link to pull request in push relay repository
Relay server: home-assistant/mobile-apps-fcm-push#278
Link to pull request in HA core
Core: home-assistant/core#166072
Any other notes
iOS version gating at 17.2. The entire feature is gated at
#available(iOS 17.2, *)— this is the minimum for push-to-start (the primary server-side start mechanism). A single availability check now covers all Live Activity APIs:supports_live_activities,frequentPushesEnabled, push-to-start token, and all ActivityKit usage. This eliminates the nested 16.2/17.2 check pattern.Push-to-start (iOS 17.2+) is client-complete. The token is observed, stored in Keychain, and included in registration payloads. All companion server-side PRs are now open — relay server at home-assistant/mobile-apps-fcm-push#278 and HA core webhook handlers at home-assistant/core#166072. The relay server uses FCM's native
apns.liveActivityTokensupport (Firebase Admin SDK v13.5.0+) — no custom APNs client or credentials needed.Live Activities entry in Settings is gated behind TestFlight. The settings row only appears when
Current.isTestFlightis true, preventing it from surfacing in a release build before the feature is fully tested. ABetaLabelbadge is shown alongside the row title.iPad:
areActivitiesEnabledis alwaysfalseon iPad — Apple system restriction. The Settings screen shows "Not available on iPad." The registry silently no-ops. HA receivessupports_live_activities: falsein the device registration for iPad.HALiveActivityAttributesis frozen post-ship. The struct name appears asattributes-typein APNs push-to-start payloads. Renaming it silently breaks all remote starts. TheContentStateCodingKeysare equally frozen — only additions are safe. Both have comments in the source calling this out.The debug section in Settings is intentional. Gated behind
#if DEBUGso it only appears in debug builds — it never ships to TestFlight or the App Store. It exercises the full ActivityKit lifecycle without requiring the server-side chain.UNUserNotificationCenterin tests. Theclear_notification+tag→ Live Activity dismissal path is covered by code review rather than a unit test.HandlerClearNotificationcallsUNUserNotificationCenter.current().removeDeliveredNotificationssynchronously, which requires a real app bundle and throwsNSInternalInconsistencyExceptionin the XCTest host. A comment in the test file explains this.Rate limiting on iOS 18. Apple throttles Live Activity updates to ~15 seconds between renders. Automations should trigger on state change events, not polling timers.
Related: