From 893dafc7464f51e6e49c2b21d01ed0960de76bc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:14:06 +0000 Subject: [PATCH 01/10] Initial plan From 80036e557e91ad98c7915c0175b7e5fc8dd84a6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:20:37 +0000 Subject: [PATCH 02/10] Add CallKit notification command for opening AssistView - Created CallKitManager to handle incoming calls via CallKit - Added 'call_assist' notification command handler - Integrated CallKit with NotificationManager to open AssistView on answer - Supports optional parameters: caller_name, pipeline_id, auto_start_recording Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- .../Notifications/NotificationManager.swift | 29 +++++ .../CallKit/CallKitManager.swift | 116 ++++++++++++++++++ .../NotificationsCommandManager.swift | 17 +++ 3 files changed, 162 insertions(+) create mode 100644 Sources/Shared/Notifications/CallKit/CallKitManager.swift diff --git a/Sources/App/Notifications/NotificationManager.swift b/Sources/App/Notifications/NotificationManager.swift index ffb47c593d..6ad39fc4f3 100644 --- a/Sources/App/Notifications/NotificationManager.swift +++ b/Sources/App/Notifications/NotificationManager.swift @@ -27,6 +27,11 @@ class NotificationManager: NSObject, LocalPushManagerDelegate { name: UIApplication.didBecomeActiveNotification, object: nil ) + + #if os(iOS) + // Set up CallKit delegate + CallKitManager.shared.delegate = self + #endif } func setupNotifications() { @@ -361,3 +366,27 @@ extension NotificationManager: MessagingDelegate { }.cauterize() } } + +#if os(iOS) +extension NotificationManager: CallKitManagerDelegate { + func callKitManager(_ manager: CallKitManager, didAnswerCallWithInfo info: [String: Any]) { + Current.Log.info("CallKit call answered, opening Assist") + + // Extract optional parameters from the call info + let pipelineId = info["pipeline_id"] as? String ?? "" + let autoStartRecording = info["auto_start_recording"] as? Bool ?? false + + // Determine which server to use + let server = Current.servers.all.first ?? Current.servers.all.first! + + // Open AssistView + Current.sceneManager.webViewWindowControllerPromise.done { windowController in + windowController.webViewExternalMessageHandler.showAssist( + server: server, + pipeline: pipelineId, + autoStartRecording: autoStartRecording + ) + } + } +} +#endif diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift new file mode 100644 index 0000000000..cdbca4ee35 --- /dev/null +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -0,0 +1,116 @@ +import AVFoundation +import CallKit +import Foundation +import PromiseKit +import Shared + +public protocol CallKitManagerDelegate: AnyObject { + func callKitManager(_ manager: CallKitManager, didAnswerCallWithInfo info: [String: Any]) +} + +public class CallKitManager: NSObject { + public static let shared = CallKitManager() + + private let provider: CXProvider + private let callController = CXCallController() + public weak var delegate: CallKitManagerDelegate? + + private var activeCallInfo: [String: Any]? + private var activeCallUUID: UUID? + + override private init() { + let config = CXProviderConfiguration() + config.supportsVideo = false + config.maximumCallGroups = 1 + config.maximumCallsPerCallGroup = 1 + config.supportedHandleTypes = [.generic] + + // Set the app name for the incoming call UI + config.localizedName = "Home Assistant" + + provider = CXProvider(configuration: config) + + super.init() + + provider.setDelegate(self, queue: nil) + } + + public func reportIncomingCall(callerName: String, userInfo: [String: Any]) -> Promise { + Promise { seal in + let uuid = UUID() + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .generic, value: callerName) + update.hasVideo = false + update.localizedCallerName = callerName + + activeCallInfo = userInfo + activeCallUUID = uuid + + provider.reportNewIncomingCall(with: uuid, update: update) { error in + if let error { + Current.Log.error("Failed to report incoming call: \(error)") + seal.reject(error) + } else { + Current.Log.info("Successfully reported incoming call") + seal.fulfill(()) + } + } + } + } + + private func endCall(uuid: UUID) { + let endCallAction = CXEndCallAction(call: uuid) + let transaction = CXTransaction(action: endCallAction) + + callController.request(transaction) { error in + if let error { + Current.Log.error("Failed to end call: \(error)") + } else { + Current.Log.info("Successfully ended call") + } + } + } +} + +extension CallKitManager: CXProviderDelegate { + public func providerDidReset(_ provider: CXProvider) { + Current.Log.info("CallKit provider did reset") + activeCallInfo = nil + activeCallUUID = nil + } + + public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { + Current.Log.info("User answered call") + + if let callInfo = activeCallInfo { + // Notify delegate that call was answered + delegate?.callKitManager(self, didAnswerCallWithInfo: callInfo) + } + + // Mark the action as fulfilled + action.fulfill() + + // End the call immediately since we just need to trigger opening Assist + if let uuid = activeCallUUID { + endCall(uuid: uuid) + } + + activeCallInfo = nil + activeCallUUID = nil + } + + public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + Current.Log.info("Call ended") + action.fulfill() + activeCallInfo = nil + activeCallUUID = nil + } + + public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + Current.Log.info("CallKit audio session activated") + } + + public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { + Current.Log.info("CallKit audio session deactivated") + } +} diff --git a/Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift b/Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift index 215ed06634..4804449bd8 100644 --- a/Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift +++ b/Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift @@ -22,6 +22,7 @@ public class NotificationCommandManager { register(command: "clear_notification", handler: HandlerClearNotification()) #if os(iOS) register(command: "update_complications", handler: HandlerUpdateComplications()) + register(command: "call_assist", handler: HandlerCallAssist()) #endif #if os(iOS) || os(macOS) @@ -128,4 +129,20 @@ private struct HandlerUpdateWidgets: NotificationCommandHandler { return Promise.value(()) } } + +private struct HandlerCallAssist: NotificationCommandHandler { + func handle(_ payload: [String: Any]) -> Promise { + Current.Log.verbose("CallKit assist triggered by notification command") + Current.clientEventStore.addEvent(ClientEvent( + text: "Notification command triggered CallKit assist", + type: .notification + )) + + // Extract caller name from payload, default to "Home Assistant" + let callerName = payload["caller_name"] as? String ?? "Home Assistant" + + // Report incoming call via CallKit + return CallKitManager.shared.reportIncomingCall(callerName: callerName, userInfo: payload) + } +} #endif From 30bfe40c3269bd73354c864aecd24d95d5140ab5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:22:28 +0000 Subject: [PATCH 03/10] Add documentation and test cases for call_assist command - Created test case JSON files for notification parser tests - Added comprehensive documentation for using the call_assist command - Included examples with various parameter combinations Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- Documentation/CallKitAssistCommand.md | 97 +++++++++++++++++++ .../command_call_assist.json | 26 +++++ .../command_call_assist_with_params.json | 26 +++++ 3 files changed, 149 insertions(+) create mode 100644 Documentation/CallKitAssistCommand.md create mode 100644 Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json create mode 100644 Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json diff --git a/Documentation/CallKitAssistCommand.md b/Documentation/CallKitAssistCommand.md new file mode 100644 index 0000000000..f6a142d1ae --- /dev/null +++ b/Documentation/CallKitAssistCommand.md @@ -0,0 +1,97 @@ +# CallKit Assist Notification Command + +This document describes the `call_assist` notification command that uses CallKit to present an incoming call UI and opens AssistView when answered. + +## Overview + +The `call_assist` command triggers a CallKit incoming call notification on iOS devices. When the user answers the call, the Home Assistant Assist interface opens automatically. + +## Use Case + +This command is useful for: +- Quickly accessing voice assistant from lock screen +- Responding to urgent home automation needs +- Hands-free interaction with Home Assistant + +## Command Format + +```yaml +service: notify.mobile_app_ +data: + message: "command" + data: + command: call_assist + # Optional parameters: + caller_name: "Home Assistant" # The name shown for the incoming call + pipeline_id: "" # Specific Assist pipeline to use + auto_start_recording: false # Start recording automatically when opened +``` + +## Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `command` | string | Yes | - | Must be `call_assist` | +| `caller_name` | string | No | "Home Assistant" | The name displayed on the incoming call screen | +| `pipeline_id` | string | No | "" | The ID of a specific Assist pipeline to use | +| `auto_start_recording` | boolean | No | false | Whether to automatically start voice recording when Assist opens | + +## Examples + +### Basic Usage + +```yaml +service: notify.mobile_app_iphone +data: + message: "command" + data: + command: call_assist +``` + +### With Custom Caller Name + +```yaml +service: notify.mobile_app_iphone +data: + message: "command" + data: + command: call_assist + caller_name: "Kitchen Motion Detected" +``` + +### With Auto-Recording + +```yaml +service: notify.mobile_app_iphone +data: + message: "command" + data: + command: call_assist + caller_name: "Front Door" + auto_start_recording: true +``` + +### With Specific Pipeline + +```yaml +service: notify.mobile_app_iphone +data: + message: "command" + data: + command: call_assist + caller_name: "Smart Assistant" + pipeline_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV" + auto_start_recording: true +``` + +## Implementation Notes + +- This command is only available on iOS (not watchOS or macOS) +- The call will automatically end once answered, as it's only used to trigger the Assist interface +- The command uses Apple's CallKit framework for native call integration +- This works even when the device is locked (subject to device settings) +- No actual call is placed - this is purely a UI trigger mechanism + +## Testing + +To test this command, send a notification with the above format from Home Assistant. When you answer the incoming call on your iOS device, the Assist interface will open automatically. diff --git a/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json b/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json new file mode 100644 index 0000000000..af33b51c25 --- /dev/null +++ b/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json @@ -0,0 +1,26 @@ +{ + "input": { + "title": "extra excluded title", + "message": "call_assist", + "registration_info": { + "app_id": "io.robbie.HomeAssistant.dev", + "os_version": "16.0", + "app_version": "2024.7" + } + }, + "rate_limit": false, + "headers": { + "apns-push-type": "background" + }, + "payload": { + "aps": { + "contentAvailable": true + }, + "homeassistant": { + "command": "call_assist", + "caller_name": "Home Assistant", + "pipeline_id": "", + "auto_start_recording": false + } + } +} diff --git a/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json b/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json new file mode 100644 index 0000000000..c72b1feb0d --- /dev/null +++ b/Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json @@ -0,0 +1,26 @@ +{ + "input": { + "title": "extra excluded title", + "message": "call_assist_with_params", + "registration_info": { + "app_id": "io.robbie.HomeAssistant.dev", + "os_version": "16.0", + "app_version": "2024.7" + } + }, + "rate_limit": false, + "headers": { + "apns-push-type": "background" + }, + "payload": { + "aps": { + "contentAvailable": true + }, + "homeassistant": { + "command": "call_assist", + "caller_name": "Kitchen Motion", + "pipeline_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "auto_start_recording": true + } + } +} From 55d6c6d5e2250be5e849806eb518e5729b8344b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:24:04 +0000 Subject: [PATCH 04/10] Fix code review issues: thread safety and error handling - Added thread-safe access to CallKit state using DispatchQueue - Fixed race condition in call answer handler by capturing state atomically - Improved error handling in NotificationManager with guard statement - Prevents crash when no servers are available Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- .../Notifications/NotificationManager.swift | 5 +++- .../CallKit/CallKitManager.swift | 27 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Sources/App/Notifications/NotificationManager.swift b/Sources/App/Notifications/NotificationManager.swift index 6ad39fc4f3..f9308a5b14 100644 --- a/Sources/App/Notifications/NotificationManager.swift +++ b/Sources/App/Notifications/NotificationManager.swift @@ -377,7 +377,10 @@ extension NotificationManager: CallKitManagerDelegate { let autoStartRecording = info["auto_start_recording"] as? Bool ?? false // Determine which server to use - let server = Current.servers.all.first ?? Current.servers.all.first! + guard let server = Current.servers.all.first else { + Current.Log.error("No servers available to open Assist") + return + } // Open AssistView Current.sceneManager.webViewWindowControllerPromise.done { windowController in diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift index cdbca4ee35..596e18788a 100644 --- a/Sources/Shared/Notifications/CallKit/CallKitManager.swift +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -15,8 +15,20 @@ public class CallKitManager: NSObject { private let callController = CXCallController() public weak var delegate: CallKitManagerDelegate? - private var activeCallInfo: [String: Any]? - private var activeCallUUID: UUID? + // Thread-safe access to call state + private let queue = DispatchQueue(label: "com.homeassistant.callkit") + private var _activeCallInfo: [String: Any]? + private var _activeCallUUID: UUID? + + private var activeCallInfo: [String: Any]? { + get { queue.sync { _activeCallInfo } } + set { queue.sync { _activeCallInfo = newValue } } + } + + private var activeCallUUID: UUID? { + get { queue.sync { _activeCallUUID } } + set { queue.sync { _activeCallUUID = newValue } } + } override private init() { let config = CXProviderConfiguration() @@ -82,7 +94,11 @@ extension CallKitManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { Current.Log.info("User answered call") - if let callInfo = activeCallInfo { + // Capture current call info atomically + let callInfo = activeCallInfo + let callUUID = activeCallUUID + + if let callInfo { // Notify delegate that call was answered delegate?.callKitManager(self, didAnswerCallWithInfo: callInfo) } @@ -91,10 +107,11 @@ extension CallKitManager: CXProviderDelegate { action.fulfill() // End the call immediately since we just need to trigger opening Assist - if let uuid = activeCallUUID { - endCall(uuid: uuid) + if let callUUID { + endCall(uuid: callUUID) } + // Clear state activeCallInfo = nil activeCallUUID = nil } From c5ddb33007cc574c345f3a41605cd25095b1baa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:25:58 +0000 Subject: [PATCH 05/10] Improve error handling and thread safety - Added error handling for webViewWindowControllerPromise failure - Improved state clearing order in CallKit answer handler - Clear state before delegate callback to prevent potential reuse - Added explicit logging for promise failures Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- .../Notifications/NotificationManager.swift | 2 ++ .../Notifications/CallKit/CallKitManager.swift | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/App/Notifications/NotificationManager.swift b/Sources/App/Notifications/NotificationManager.swift index f9308a5b14..5d6ec8120d 100644 --- a/Sources/App/Notifications/NotificationManager.swift +++ b/Sources/App/Notifications/NotificationManager.swift @@ -389,6 +389,8 @@ extension NotificationManager: CallKitManagerDelegate { pipeline: pipelineId, autoStartRecording: autoStartRecording ) + }.catch { error in + Current.Log.error("Failed to open Assist from CallKit: \(error)") } } } diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift index 596e18788a..af930c717e 100644 --- a/Sources/Shared/Notifications/CallKit/CallKitManager.swift +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -94,26 +94,26 @@ extension CallKitManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { Current.Log.info("User answered call") - // Capture current call info atomically + // Capture current call info atomically before clearing let callInfo = activeCallInfo let callUUID = activeCallUUID - if let callInfo { - // Notify delegate that call was answered - delegate?.callKitManager(self, didAnswerCallWithInfo: callInfo) - } + // Clear state immediately to prevent reuse + activeCallInfo = nil + activeCallUUID = nil // Mark the action as fulfilled action.fulfill() + // Notify delegate with captured state (happens after state is cleared for thread safety) + if let callInfo { + delegate?.callKitManager(self, didAnswerCallWithInfo: callInfo) + } + // End the call immediately since we just need to trigger opening Assist if let callUUID { endCall(uuid: callUUID) } - - // Clear state - activeCallInfo = nil - activeCallUUID = nil } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { From 6f3442eafd47616b7bba19a2095aa3a7861c174b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:28:08 +0000 Subject: [PATCH 06/10] Fix thread safety with atomic state operations - Added atomic captureAndClearState() method for thread-safe state transitions - Added clearState() helper for atomic state clearing - All state operations now happen within a single queue.sync block - Prevents race conditions between reading and clearing state Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- .../CallKit/CallKitManager.swift | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift index af930c717e..5c845a39b7 100644 --- a/Sources/Shared/Notifications/CallKit/CallKitManager.swift +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -30,6 +30,25 @@ public class CallKitManager: NSObject { set { queue.sync { _activeCallUUID = newValue } } } + // Atomically capture and clear call state + private func captureAndClearState() -> (info: [String: Any]?, uuid: UUID?) { + queue.sync { + let info = _activeCallInfo + let uuid = _activeCallUUID + _activeCallInfo = nil + _activeCallUUID = nil + return (info, uuid) + } + } + + // Atomically clear call state + private func clearState() { + queue.sync { + _activeCallInfo = nil + _activeCallUUID = nil + } + } + override private init() { let config = CXProviderConfiguration() config.supportsVideo = false @@ -87,25 +106,19 @@ public class CallKitManager: NSObject { extension CallKitManager: CXProviderDelegate { public func providerDidReset(_ provider: CXProvider) { Current.Log.info("CallKit provider did reset") - activeCallInfo = nil - activeCallUUID = nil + clearState() } public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { Current.Log.info("User answered call") - // Capture current call info atomically before clearing - let callInfo = activeCallInfo - let callUUID = activeCallUUID - - // Clear state immediately to prevent reuse - activeCallInfo = nil - activeCallUUID = nil + // Atomically capture and clear call state + let (callInfo, callUUID) = captureAndClearState() // Mark the action as fulfilled action.fulfill() - // Notify delegate with captured state (happens after state is cleared for thread safety) + // Notify delegate with captured state if let callInfo { delegate?.callKitManager(self, didAnswerCallWithInfo: callInfo) } @@ -119,8 +132,7 @@ extension CallKitManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { Current.Log.info("Call ended") action.fulfill() - activeCallInfo = nil - activeCallUUID = nil + clearState() } public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { From 6c92e41101318676a683bececee8b8f0e574c6d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:29:12 +0000 Subject: [PATCH 07/10] Add implementation summary documentation - Comprehensive overview of CallKit notification command feature - Details on all files changed and their purposes - Technical implementation details and architecture - Code quality notes and future enhancement ideas Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com> --- IMPLEMENTATION_SUMMARY.md | 139 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 IMPLEMENTATION_SUMMARY.md diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000000..7c2533be8b --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,139 @@ +# CallKit Notification Command Implementation Summary + +## Overview +This implementation adds a new notification command `call_assist` that uses Apple's CallKit framework to present an incoming call UI on iOS devices. When the user answers the call, the Home Assistant Assist interface opens automatically. + +## Files Changed + +### New Files Created + +1. **`Sources/Shared/Notifications/CallKit/CallKitManager.swift`** + - Core CallKit integration manager + - Handles incoming call presentation using CXProvider + - Thread-safe state management with DispatchQueue + - Delegates call answer events to NotificationManager + - Automatically ends the call after answer + +2. **`Documentation/CallKitAssistCommand.md`** + - Comprehensive documentation for the new command + - Usage examples with various parameter combinations + - Implementation notes and limitations + +3. **Test Case Files** + - `Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json` + - `Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json` + +### Modified Files + +1. **`Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift`** + - Added new `HandlerCallAssist` command handler + - Registered `call_assist` command in iOS-only section + - Extracts optional parameters: `caller_name`, `pipeline_id`, `auto_start_recording` + - Triggers CallKit incoming call when command is received + +2. **`Sources/App/Notifications/NotificationManager.swift`** + - Implements `CallKitManagerDelegate` protocol + - Sets up CallKit delegate in initialization + - Opens AssistView when call is answered + - Proper error handling for missing servers and promise failures + +## Key Features + +### Thread Safety +- All CallKit state operations are protected with a dedicated DispatchQueue +- Atomic `captureAndClearState()` method ensures race-free state transitions +- Separate computed properties for thread-safe get/set operations + +### Error Handling +- Guard statement prevents crash when no servers are available +- Promise error handling with explicit logging +- CallKit API error handling with detailed logging + +### Parameters +The command supports the following optional parameters: +- `caller_name`: The name displayed on the incoming call screen (default: "Home Assistant") +- `pipeline_id`: The ID of a specific Assist pipeline to use (default: "") +- `auto_start_recording`: Whether to automatically start voice recording (default: false) + +## Usage Example + +```yaml +service: notify.mobile_app_iphone +data: + message: "command" + data: + command: call_assist + caller_name: "Front Door Camera" + auto_start_recording: true +``` + +## Technical Implementation Details + +### CallKit Integration +1. Creates a CXProvider with configuration for generic calls +2. Reports incoming call with customizable caller name +3. Handles CXAnswerCallAction to detect when user answers +4. Immediately ends the call after answer (it's just a trigger) +5. Supports CallKit delegate methods for proper lifecycle management + +### State Management +- Uses private `_activeCallInfo` and `_activeCallUUID` for internal state +- Provides thread-safe public accessors via computed properties +- Atomic operations for capturing and clearing state prevent race conditions + +### Notification Flow +1. Home Assistant sends push notification with `call_assist` command +2. NotificationCommandManager receives and parses the command +3. HandlerCallAssist extracts parameters and calls CallKitManager +4. CallKitManager presents native iOS incoming call UI +5. User answers the call +6. CallKitManagerDelegate (NotificationManager) is notified +7. AssistView is opened with specified parameters +8. Call is automatically ended + +## Code Quality + +### Code Review Issues Addressed +✅ Thread safety with DispatchQueue and atomic operations +✅ Race condition prevention in state management +✅ Proper error handling throughout the call chain +✅ No force unwrapping of optionals +✅ Clean separation of concerns + +### Testing +- Test case JSON files added for notification parser +- Can be manually tested by sending push notifications from Home Assistant + +## Future Enhancements + +Potential improvements for future iterations: +1. Support for server selection from notification payload +2. Custom call sounds or vibration patterns +3. CallKit call history integration +4. Support for multiple concurrent calls (if needed) +5. Localization of caller name strings + +## Compatibility + +- **Platform**: iOS only (uses CallKit framework) +- **Minimum iOS Version**: iOS 10.0+ (CallKit availability) +- **Works on**: Lock screen, home screen, and when app is in background +- **Requires**: Remote notifications enabled +- **Similar to**: `update_widgets` command (background notification command pattern) + +## Security Considerations + +- No actual call is placed - this is purely a UI trigger +- Uses existing Home Assistant authentication and server connection +- CallKit does not require additional permissions beyond notifications +- State is cleared immediately after use to prevent reuse +- Thread-safe implementation prevents data races + +## Documentation + +Complete documentation is available in `Documentation/CallKitAssistCommand.md` with: +- Overview and use cases +- Complete parameter reference +- Multiple usage examples +- Implementation notes +- Testing instructions From 005db02b0c9478f2f478d79c622cf9978ef8fd1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:29:47 +0000 Subject: [PATCH 08/10] Add flow diagram documentation for CallKit Assist feature --- Documentation/CallKitAssistFlowDiagram.md | 130 ++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Documentation/CallKitAssistFlowDiagram.md diff --git a/Documentation/CallKitAssistFlowDiagram.md b/Documentation/CallKitAssistFlowDiagram.md new file mode 100644 index 0000000000..cde8fc2137 --- /dev/null +++ b/Documentation/CallKitAssistFlowDiagram.md @@ -0,0 +1,130 @@ +# CallKit Assist Flow Diagram + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ User Experience Flow │ +└─────────────────────────────────────────────────────────────────┘ + +1. Home Assistant sends notification + ┌──────────────────────────────────┐ + │ Home Assistant Server │ + │ notify.mobile_app_iphone │ + │ command: call_assist │ + │ caller_name: "Front Door" │ + └──────────────┬───────────────────┘ + │ + ▼ +2. iOS receives push notification + ┌──────────────────────────────────┐ + │ iOS Device │ + │ (Even if locked) │ + └──────────────┬───────────────────┘ + │ + ▼ +3. CallKit presents incoming call + ┌──────────────────────────────────┐ + │ ╔════════════════════════════╗ │ + │ ║ Incoming Call ║ │ + │ ║ ║ │ + │ ║ 🏠 ║ │ + │ ║ ║ │ + │ ║ Front Door ║ │ + │ ║ Home Assistant ║ │ + │ ║ ║ │ + │ ║ [Decline] [Accept] ║ │ + │ ╚════════════════════════════╝ │ + └──────────────┬───────────────────┘ + │ + ▼ +4. User answers the call + ┌──────────────────────────────────┐ + │ User taps "Accept" │ + └──────────────┬───────────────────┘ + │ + ▼ +5. Assist opens automatically + ┌──────────────────────────────────┐ + │ ╔════════════════════════════╗ │ + │ ║ Assist ⚙️ ║ │ + │ ║ ║ │ + │ ║ 🎙️ Recording... ║ │ + │ ║ ║ │ + │ ║ [Voice input area] ║ │ + │ ║ ║ │ + │ ║ "How can I help?" ║ │ + │ ║ ║ │ + │ ╚════════════════════════════╝ │ + └──────────────────────────────────┘ + +6. Call ends automatically + - No actual call was placed + - User can now interact with Assist + + +┌─────────────────────────────────────────────────────────────────┐ +│ Technical Flow │ +└─────────────────────────────────────────────────────────────────┘ + + HA Notification Server + │ + ▼ + NotificationManager.handleRemoteNotification() + │ + ▼ + NotificationCommandManager.handle() + │ + ▼ + HandlerCallAssist.handle() + │ + ▼ + CallKitManager.reportIncomingCall() + │ + ▼ + CXProvider presents native call UI + │ + ▼ + User answers (CXAnswerCallAction) + │ + ▼ + CallKitManager.provider(perform: CXAnswerCallAction) + │ + ├──> captureAndClearState() (thread-safe) + │ + ├──> action.fulfill() + │ + ├──> delegate?.callKitManager(didAnswerCallWithInfo:) + │ + └──> endCall(uuid:) + + │ + ▼ + NotificationManager.callKitManager(didAnswerCallWithInfo:) + │ + ├──> Extract parameters (pipelineId, autoStartRecording) + │ + ├──> Get server + │ + └──> webViewExternalMessageHandler.showAssist() + │ + ▼ + AssistView opens with parameters + + +┌─────────────────────────────────────────────────────────────────┐ +│ Comparison with update_widgets │ +└─────────────────────────────────────────────────────────────────┘ + +update_widgets command: + • Silent background notification + • Refreshes widget data + • No user interaction required + • No visible UI + +call_assist command: + • Interactive CallKit UI + • Opens app to AssistView + • User must answer the call + • Native iOS call experience + • Works from lock screen + • Gets user's attention immediately +``` From fd7078386571d1bd2bd85b3c86d27e44e5a94ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o?= <5808343+bgoncal@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:53:09 +0100 Subject: [PATCH 09/10] Remove CallKit Assist documentation and update CallKit integration Deleted documentation files related to the CallKit Assist command and implementation summary. Updated Xcode project to add CallKitManager.swift to the build and source groups. Refactored NotificationManager to use webViewControllerPromise for AssistView presentation. Cleaned up CallKitManager by removing hardcoded app name from provider configuration. --- Documentation/CallKitAssistCommand.md | 97 ------------ Documentation/CallKitAssistFlowDiagram.md | 130 ---------------- HomeAssistant.xcodeproj/project.pbxproj | 12 ++ IMPLEMENTATION_SUMMARY.md | 139 ------------------ .../Notifications/NotificationManager.swift | 4 +- .../CallKit/CallKitManager.swift | 4 - 6 files changed, 14 insertions(+), 372 deletions(-) delete mode 100644 Documentation/CallKitAssistCommand.md delete mode 100644 Documentation/CallKitAssistFlowDiagram.md delete mode 100644 IMPLEMENTATION_SUMMARY.md diff --git a/Documentation/CallKitAssistCommand.md b/Documentation/CallKitAssistCommand.md deleted file mode 100644 index f6a142d1ae..0000000000 --- a/Documentation/CallKitAssistCommand.md +++ /dev/null @@ -1,97 +0,0 @@ -# CallKit Assist Notification Command - -This document describes the `call_assist` notification command that uses CallKit to present an incoming call UI and opens AssistView when answered. - -## Overview - -The `call_assist` command triggers a CallKit incoming call notification on iOS devices. When the user answers the call, the Home Assistant Assist interface opens automatically. - -## Use Case - -This command is useful for: -- Quickly accessing voice assistant from lock screen -- Responding to urgent home automation needs -- Hands-free interaction with Home Assistant - -## Command Format - -```yaml -service: notify.mobile_app_ -data: - message: "command" - data: - command: call_assist - # Optional parameters: - caller_name: "Home Assistant" # The name shown for the incoming call - pipeline_id: "" # Specific Assist pipeline to use - auto_start_recording: false # Start recording automatically when opened -``` - -## Parameters - -| Parameter | Type | Required | Default | Description | -|-----------|------|----------|---------|-------------| -| `command` | string | Yes | - | Must be `call_assist` | -| `caller_name` | string | No | "Home Assistant" | The name displayed on the incoming call screen | -| `pipeline_id` | string | No | "" | The ID of a specific Assist pipeline to use | -| `auto_start_recording` | boolean | No | false | Whether to automatically start voice recording when Assist opens | - -## Examples - -### Basic Usage - -```yaml -service: notify.mobile_app_iphone -data: - message: "command" - data: - command: call_assist -``` - -### With Custom Caller Name - -```yaml -service: notify.mobile_app_iphone -data: - message: "command" - data: - command: call_assist - caller_name: "Kitchen Motion Detected" -``` - -### With Auto-Recording - -```yaml -service: notify.mobile_app_iphone -data: - message: "command" - data: - command: call_assist - caller_name: "Front Door" - auto_start_recording: true -``` - -### With Specific Pipeline - -```yaml -service: notify.mobile_app_iphone -data: - message: "command" - data: - command: call_assist - caller_name: "Smart Assistant" - pipeline_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV" - auto_start_recording: true -``` - -## Implementation Notes - -- This command is only available on iOS (not watchOS or macOS) -- The call will automatically end once answered, as it's only used to trigger the Assist interface -- The command uses Apple's CallKit framework for native call integration -- This works even when the device is locked (subject to device settings) -- No actual call is placed - this is purely a UI trigger mechanism - -## Testing - -To test this command, send a notification with the above format from Home Assistant. When you answer the incoming call on your iOS device, the Assist interface will open automatically. diff --git a/Documentation/CallKitAssistFlowDiagram.md b/Documentation/CallKitAssistFlowDiagram.md deleted file mode 100644 index cde8fc2137..0000000000 --- a/Documentation/CallKitAssistFlowDiagram.md +++ /dev/null @@ -1,130 +0,0 @@ -# CallKit Assist Flow Diagram - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ User Experience Flow │ -└─────────────────────────────────────────────────────────────────┘ - -1. Home Assistant sends notification - ┌──────────────────────────────────┐ - │ Home Assistant Server │ - │ notify.mobile_app_iphone │ - │ command: call_assist │ - │ caller_name: "Front Door" │ - └──────────────┬───────────────────┘ - │ - ▼ -2. iOS receives push notification - ┌──────────────────────────────────┐ - │ iOS Device │ - │ (Even if locked) │ - └──────────────┬───────────────────┘ - │ - ▼ -3. CallKit presents incoming call - ┌──────────────────────────────────┐ - │ ╔════════════════════════════╗ │ - │ ║ Incoming Call ║ │ - │ ║ ║ │ - │ ║ 🏠 ║ │ - │ ║ ║ │ - │ ║ Front Door ║ │ - │ ║ Home Assistant ║ │ - │ ║ ║ │ - │ ║ [Decline] [Accept] ║ │ - │ ╚════════════════════════════╝ │ - └──────────────┬───────────────────┘ - │ - ▼ -4. User answers the call - ┌──────────────────────────────────┐ - │ User taps "Accept" │ - └──────────────┬───────────────────┘ - │ - ▼ -5. Assist opens automatically - ┌──────────────────────────────────┐ - │ ╔════════════════════════════╗ │ - │ ║ Assist ⚙️ ║ │ - │ ║ ║ │ - │ ║ 🎙️ Recording... ║ │ - │ ║ ║ │ - │ ║ [Voice input area] ║ │ - │ ║ ║ │ - │ ║ "How can I help?" ║ │ - │ ║ ║ │ - │ ╚════════════════════════════╝ │ - └──────────────────────────────────┘ - -6. Call ends automatically - - No actual call was placed - - User can now interact with Assist - - -┌─────────────────────────────────────────────────────────────────┐ -│ Technical Flow │ -└─────────────────────────────────────────────────────────────────┘ - - HA Notification Server - │ - ▼ - NotificationManager.handleRemoteNotification() - │ - ▼ - NotificationCommandManager.handle() - │ - ▼ - HandlerCallAssist.handle() - │ - ▼ - CallKitManager.reportIncomingCall() - │ - ▼ - CXProvider presents native call UI - │ - ▼ - User answers (CXAnswerCallAction) - │ - ▼ - CallKitManager.provider(perform: CXAnswerCallAction) - │ - ├──> captureAndClearState() (thread-safe) - │ - ├──> action.fulfill() - │ - ├──> delegate?.callKitManager(didAnswerCallWithInfo:) - │ - └──> endCall(uuid:) - - │ - ▼ - NotificationManager.callKitManager(didAnswerCallWithInfo:) - │ - ├──> Extract parameters (pipelineId, autoStartRecording) - │ - ├──> Get server - │ - └──> webViewExternalMessageHandler.showAssist() - │ - ▼ - AssistView opens with parameters - - -┌─────────────────────────────────────────────────────────────────┐ -│ Comparison with update_widgets │ -└─────────────────────────────────────────────────────────────────┘ - -update_widgets command: - • Silent background notification - • Refreshes widget data - • No user interaction required - • No visible UI - -call_assist command: - • Interactive CallKit UI - • Opens app to AssistView - • User must answer the call - • Native iOS call experience - • Works from lock screen - • Gets user's attention immediately -``` diff --git a/HomeAssistant.xcodeproj/project.pbxproj b/HomeAssistant.xcodeproj/project.pbxproj index 4627ea5c22..b152172ba5 100644 --- a/HomeAssistant.xcodeproj/project.pbxproj +++ b/HomeAssistant.xcodeproj/project.pbxproj @@ -906,6 +906,7 @@ 42955C252F1A552A00E398E8 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 420BE1382F0FE50C00E20584 /* ToastView.swift */; }; 42955C262F1A552A00E398E8 /* ToastHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 420BE1402F0FE5B000E20584 /* ToastHostingController.swift */; }; 42955C272F1A552A00E398E8 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 420BE1372F0FE50C00E20584 /* Toast.swift */; }; + 42955C352F1E25FB00E398E8 /* CallKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42955C322F1E25FB00E398E8 /* CallKitManager.swift */; }; 4296C36D2B90DB640051B63C /* IntentActionAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */; }; 4296C36E2B90DB640051B63C /* PerformAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36C2B90DB630051B63C /* PerformAction.swift */; }; 4296C3762B91F0F50051B63C /* WidgetActionsAppIntentTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */; }; @@ -2551,6 +2552,7 @@ 429481EA2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewJavascriptCommandsTests.swift; sourceTree = ""; }; 429481EC2DA943E700A8B468 /* ListPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPicker.swift; sourceTree = ""; }; 429481EE2DA94B9900A8B468 /* ListPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPickerTests.swift; sourceTree = ""; }; + 42955C322F1E25FB00E398E8 /* CallKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallKitManager.swift; sourceTree = ""; }; 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentActionAppEntity.swift; sourceTree = ""; }; 4296C36C2B90DB630051B63C /* PerformAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformAction.swift; sourceTree = ""; }; 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActionsAppIntentTimelineProvider.swift; sourceTree = ""; }; @@ -5323,6 +5325,14 @@ path = Audio; sourceTree = ""; }; + 42955C332F1E25FB00E398E8 /* CallKit */ = { + isa = PBXGroup; + children = ( + 42955C322F1E25FB00E398E8 /* CallKitManager.swift */, + ); + path = CallKit; + sourceTree = ""; + }; 4296C36A2B90DB630051B63C /* AppIntents */ = { isa = PBXGroup; children = ( @@ -7263,6 +7273,7 @@ D0EEF325214DF30D00D1D360 /* Notifications */ = { isa = PBXGroup; children = ( + 42955C332F1E25FB00E398E8 /* CallKit */, 420F53E22C4E61C1003C8415 /* LocalNotificationDispatcher.swift */, 425573EE2B589B0F00145217 /* NotificationIdentifier.swift */, 11ADF93D267D34A20040A7E3 /* NotificationCommands */, @@ -9944,6 +9955,7 @@ 11CB98CA249E62E700B05222 /* Version+HA.swift in Sources */, 420F53EA2C4E9D54003C8415 /* WidgetsKind.swift in Sources */, 11EE9B4924C5116F00404AF8 /* LegacyModelManager.swift in Sources */, + 42955C352F1E25FB00E398E8 /* CallKitManager.swift in Sources */, 46C62BA12D8A3799002C0001 /* SnapshottablePreviewConfigurations.swift in Sources */, 42CE8FB62B46D14C00C707F9 /* FrontendStrings+Values.swift in Sources */, D0C3DC142134CD4E000C9EE1 /* CMMotion+StringExtensions.swift in Sources */, diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 7c2533be8b..0000000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,139 +0,0 @@ -# CallKit Notification Command Implementation Summary - -## Overview -This implementation adds a new notification command `call_assist` that uses Apple's CallKit framework to present an incoming call UI on iOS devices. When the user answers the call, the Home Assistant Assist interface opens automatically. - -## Files Changed - -### New Files Created - -1. **`Sources/Shared/Notifications/CallKit/CallKitManager.swift`** - - Core CallKit integration manager - - Handles incoming call presentation using CXProvider - - Thread-safe state management with DispatchQueue - - Delegates call answer events to NotificationManager - - Automatically ends the call after answer - -2. **`Documentation/CallKitAssistCommand.md`** - - Comprehensive documentation for the new command - - Usage examples with various parameter combinations - - Implementation notes and limitations - -3. **Test Case Files** - - `Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist.json` - - `Sources/PushServer/Tests/SharedPushTests/notification_test_cases.bundle/command_call_assist_with_params.json` - -### Modified Files - -1. **`Sources/Shared/Notifications/NotificationCommands/NotificationsCommandManager.swift`** - - Added new `HandlerCallAssist` command handler - - Registered `call_assist` command in iOS-only section - - Extracts optional parameters: `caller_name`, `pipeline_id`, `auto_start_recording` - - Triggers CallKit incoming call when command is received - -2. **`Sources/App/Notifications/NotificationManager.swift`** - - Implements `CallKitManagerDelegate` protocol - - Sets up CallKit delegate in initialization - - Opens AssistView when call is answered - - Proper error handling for missing servers and promise failures - -## Key Features - -### Thread Safety -- All CallKit state operations are protected with a dedicated DispatchQueue -- Atomic `captureAndClearState()` method ensures race-free state transitions -- Separate computed properties for thread-safe get/set operations - -### Error Handling -- Guard statement prevents crash when no servers are available -- Promise error handling with explicit logging -- CallKit API error handling with detailed logging - -### Parameters -The command supports the following optional parameters: -- `caller_name`: The name displayed on the incoming call screen (default: "Home Assistant") -- `pipeline_id`: The ID of a specific Assist pipeline to use (default: "") -- `auto_start_recording`: Whether to automatically start voice recording (default: false) - -## Usage Example - -```yaml -service: notify.mobile_app_iphone -data: - message: "command" - data: - command: call_assist - caller_name: "Front Door Camera" - auto_start_recording: true -``` - -## Technical Implementation Details - -### CallKit Integration -1. Creates a CXProvider with configuration for generic calls -2. Reports incoming call with customizable caller name -3. Handles CXAnswerCallAction to detect when user answers -4. Immediately ends the call after answer (it's just a trigger) -5. Supports CallKit delegate methods for proper lifecycle management - -### State Management -- Uses private `_activeCallInfo` and `_activeCallUUID` for internal state -- Provides thread-safe public accessors via computed properties -- Atomic operations for capturing and clearing state prevent race conditions - -### Notification Flow -1. Home Assistant sends push notification with `call_assist` command -2. NotificationCommandManager receives and parses the command -3. HandlerCallAssist extracts parameters and calls CallKitManager -4. CallKitManager presents native iOS incoming call UI -5. User answers the call -6. CallKitManagerDelegate (NotificationManager) is notified -7. AssistView is opened with specified parameters -8. Call is automatically ended - -## Code Quality - -### Code Review Issues Addressed -✅ Thread safety with DispatchQueue and atomic operations -✅ Race condition prevention in state management -✅ Proper error handling throughout the call chain -✅ No force unwrapping of optionals -✅ Clean separation of concerns - -### Testing -- Test case JSON files added for notification parser -- Can be manually tested by sending push notifications from Home Assistant - -## Future Enhancements - -Potential improvements for future iterations: -1. Support for server selection from notification payload -2. Custom call sounds or vibration patterns -3. CallKit call history integration -4. Support for multiple concurrent calls (if needed) -5. Localization of caller name strings - -## Compatibility - -- **Platform**: iOS only (uses CallKit framework) -- **Minimum iOS Version**: iOS 10.0+ (CallKit availability) -- **Works on**: Lock screen, home screen, and when app is in background -- **Requires**: Remote notifications enabled -- **Similar to**: `update_widgets` command (background notification command pattern) - -## Security Considerations - -- No actual call is placed - this is purely a UI trigger -- Uses existing Home Assistant authentication and server connection -- CallKit does not require additional permissions beyond notifications -- State is cleared immediately after use to prevent reuse -- Thread-safe implementation prevents data races - -## Documentation - -Complete documentation is available in `Documentation/CallKitAssistCommand.md` with: -- Overview and use cases -- Complete parameter reference -- Multiple usage examples -- Implementation notes -- Testing instructions diff --git a/Sources/App/Notifications/NotificationManager.swift b/Sources/App/Notifications/NotificationManager.swift index 5d6ec8120d..7c0acf5e11 100644 --- a/Sources/App/Notifications/NotificationManager.swift +++ b/Sources/App/Notifications/NotificationManager.swift @@ -383,8 +383,8 @@ extension NotificationManager: CallKitManagerDelegate { } // Open AssistView - Current.sceneManager.webViewWindowControllerPromise.done { windowController in - windowController.webViewExternalMessageHandler.showAssist( + Current.sceneManager.webViewWindowControllerPromise.then(\.webViewControllerPromise).done { webViewController in + webViewController.webViewExternalMessageHandler.showAssist( server: server, pipeline: pipelineId, autoStartRecording: autoStartRecording diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift index 5c845a39b7..34fe0829d6 100644 --- a/Sources/Shared/Notifications/CallKit/CallKitManager.swift +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -2,7 +2,6 @@ import AVFoundation import CallKit import Foundation import PromiseKit -import Shared public protocol CallKitManagerDelegate: AnyObject { func callKitManager(_ manager: CallKitManager, didAnswerCallWithInfo info: [String: Any]) @@ -56,9 +55,6 @@ public class CallKitManager: NSObject { config.maximumCallsPerCallGroup = 1 config.supportedHandleTypes = [.generic] - // Set the app name for the incoming call UI - config.localizedName = "Home Assistant" - provider = CXProvider(configuration: config) super.init() From 42ab2ea734caea9cfdeb7aad6d5574b2bfe7def4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o?= <5808343+bgoncal@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:53:25 +0100 Subject: [PATCH 10/10] Lint --- Sources/Shared/Notifications/CallKit/CallKitManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Shared/Notifications/CallKit/CallKitManager.swift b/Sources/Shared/Notifications/CallKit/CallKitManager.swift index 34fe0829d6..bb08f041cc 100644 --- a/Sources/Shared/Notifications/CallKit/CallKitManager.swift +++ b/Sources/Shared/Notifications/CallKit/CallKitManager.swift @@ -55,7 +55,7 @@ public class CallKitManager: NSObject { config.maximumCallsPerCallGroup = 1 config.supportedHandleTypes = [.generic] - provider = CXProvider(configuration: config) + self.provider = CXProvider(configuration: config) super.init()