Skip to content

Add custom notification icons for automations#4672

Open
Roei-Bracha wants to merge 10 commits into
home-assistant:mainfrom
Roei-Bracha:feature/custom-notification-icons
Open

Add custom notification icons for automations#4672
Roei-Bracha wants to merge 10 commits into
home-assistant:mainfrom
Roei-Bracha:feature/custom-notification-icons

Conversation

@Roei-Bracha
Copy link
Copy Markdown

@Roei-Bracha Roei-Bracha commented May 27, 2026

This PR adds support for custom notification icons (iMessage-style communication notifications) when a push payload contains notification_icon or icon_url. Matches the Android companion app's payload schema.

Simulator Screenshot - iPhone 16e - 2026-05-28 at 22 07 51

Testing status

This currently works with local push. I verified it in the iOS simulator by sending a Home Assistant notification with:

data:
  notification_icon: mdi:cellphone

After enabling local push for the simulator server, the notification displayed the custom cellphone icon instead of the app icon.

Nabu Casa / remote push rollout

For Nabu Casa cloud push and other non-local notifications to work, the push relay/server must deploy the matching SharedPush parser change from this PR. The server-side notification normalization currently needs to:

  • copy icon_url, notification_icon, notification_icon_color, and color from the Home Assistant notification data object into the top-level APNs custom payload
  • set aps["mutable-content"] = true when either icon_url or notification_icon is present, so iOS launches the notification service extension

Expected APNs payload shape for notification_icon: mdi:cellphone:

{
  "aps": {
    "alert": {
      "title": "Phone",
      "body": "test"
    },
    "sound": "default",
    "mutable-content": true
  },
  "notification_icon": "mdi:cellphone",
  "notification_icon_color": "#FFFFFF",
  "color": "#03A9F4"
}

Until that server-side relay code is deployed, cloud pushes can still arrive without notification_icon and without mutable-content, so they will keep showing the normal app icon.

Copilot AI review requested due to automatic review settings May 27, 2026 17:23
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 @Roei-Bracha

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 May 27, 2026 17:23
@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.

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

Note

Copilot was unable to run its full agentic suite in this review.

Adds end-to-end support for custom notification “sender avatars” on iOS by parsing existing Android payload fields (notification_icon, color, icon_url) and decorating notifications into iOS Communication Notification style using INSendMessageIntent, with caching + tests and accompanying design/plan docs.

Changes:

  • Introduces NotificationSenderInfo + NotificationSenderParser to extract sender/icon info from UNNotificationContent.userInfo.
  • Adds NotificationCommunicationDecorator to apply Communication Notification styling (MDI render or downloaded URL icon) and a disk-backed NotificationIconCache.
  • Wires the decorator into the Notification Service Extension pipeline and adds unit tests + a sample .apns payload.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
docs/superpowers/specs/2026-05-24-custom-notification-icons-design.md New design spec describing payload contract, architecture, caching, and compliance considerations
docs/superpowers/plans/2026-05-24-custom-notification-icons.md New task-by-task implementation plan and test strategy
Sources/Shared/Notifications/NotificationSender/NotificationSenderInfo.swift Defines parsed sender/icon value type used by parser/decorator
Sources/Shared/Notifications/NotificationSender/NotificationSenderParser.swift Parses notification_icon / icon_url / colors into NotificationSenderInfo
Sources/Shared/Notifications/NotificationSender/NotificationIconCache.swift Adds disk cache with App Group + cross-process coordination
Sources/Shared/Notifications/NotificationSender/NotificationCommunicationDecorator.swift Builds/donates INSendMessageIntent and applies content.updating(from:) with avatar image generation
Sources/Shared/Environment/Environment.swift Registers decorator in DI container (Current)
Sources/Extensions/NotificationService/NotificationService.swift Adds decorator step after existing attachment pipeline
Sources/Extensions/NotificationContent/Resources/TestNotifications/communication_notification.apns Adds reproducible test payload for manual simulator/device verification
Tests/Shared/NotificationSender/NotificationSenderInfoTests.swift Unit tests for value-type equality
Tests/Shared/NotificationSender/NotificationSenderParserTests.swift Unit tests for field precedence, defaults, and malformed inputs
Tests/Shared/NotificationSender/NotificationIconCacheTests.swift Unit tests for cache behavior + eviction + key stability
Tests/Shared/NotificationSender/NotificationCommunicationDecoratorTests.swift Unit tests for intent construction, conversation grouping, URL download/cache behavior
HomeAssistant.xcodeproj/project.pbxproj Adds new files to targets/groups (and incidental project-file churn)
Gemfile Adds abbrev gem dependency
Gemfile.lock Locks abbrev and bumps Bundler version

Comment thread Tests/Shared/NotificationSender/NotificationIconCacheTests.swift Outdated
Comment thread docs/superpowers/plans/2026-05-24-custom-notification-icons.md Outdated
@Roei-Bracha Roei-Bracha force-pushed the feature/custom-notification-icons branch from 7eb3bac to 7ccd851 Compare May 27, 2026 17:26
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 @Roei-Bracha

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!

@Roei-Bracha Roei-Bracha requested a review from Copilot May 27, 2026 17:43
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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Copilot reviewed 13 out of 14 changed files in this pull request and generated 2 comments.

Comment on lines +9 to +24
public struct NotificationSenderInfo: Equatable {
public enum Source: Equatable {
/// User-supplied image URL. `needsAuth` is true when the URL string begins with `/`
/// (matching the rule in `NotificationAttachmentParserURL`).
case iconURL(URL, needsAuth: Bool)

/// Built-in Material Design Icon, rendered onto a colored square.
/// `background` defaults to `AppConstants.tintColor` when `color` is absent.
/// `foreground` defaults to `.white` when `notification_icon_color` is absent.
case mdi(
name: String,
background: UIColor,
foreground: UIColor,
colorString: String?,
iconColorString: String?
)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. We have implemented a manual == operator for NotificationSenderInfo.Source that compares MDI icon names, string values (colorString, iconColorString) directly if present, and safely compares UIColors via isEqual as a fallback. This guarantees clean and stable synthesized Equatable behavior.

Comment on lines +108 to +112
case let .iconURL(url, needsAuth):
let cacheKey = notificationIconCacheKey(for: url)
if let cached = cache.data(forKey: cacheKey) {
return .value(INImage(imageData: cached))
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. We modified notificationIconCacheKey(for:serverID:) to accept an optional serverID: String? = nil parameter. When resolving relative/authenticated icon URLs, we extract the active server's unique identifier from the HomeAssistantAPI instance and include it in the hashed string, preventing namespace collisions and leaks across multiple servers in the shared App Group container.

@bgoncal
Copy link
Copy Markdown
Member

bgoncal commented May 28, 2026

For the linter you can run bundle exec fastlane autocorrect

@bgoncal
Copy link
Copy Markdown
Member

bgoncal commented May 28, 2026

Please mark as "Ready for review" after you finish

- Support 'notification_icon', 'notification_icon_color', and 'color' keys in the push payload.
- Promote custom notification icon fields in local push / websocket parser.
- Register 'INSendMessageIntent' in NotificationService Extension Info.plist to support communication notification decoration.
- Ensure notification intent donation completes before finishing decoration.
- Add unit tests for local push event, notification sender parser, and legacy parser.
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.

3 participants