Skip to content

Remove friendly name usage from HAControls - #7268

Open
TimoPtr wants to merge 2 commits into
feature/wear_remove_friendly_namefrom
feature/ha_controls_remove_friendly_name
Open

Remove friendly name usage from HAControls#7268
TimoPtr wants to merge 2 commits into
feature/wear_remove_friendly_namefrom
feature/ha_controls_remove_friendly_name

Conversation

@TimoPtr

@TimoPtr TimoPtr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Remove friendly_name usage in HAControls it was accessing it directly through the attributes of the Entity. It uses instead the EntitiesForDisplayManager that gives everything required for the HaControls. I had to extend the EntityDisplay interface to add more attributes.

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.
  • I have read the Open Home Foundation AI Policy.

Select exactly one option that describes AI usage in this contribution:

  • I have not used AI for this contribution.
  • AI assistance was used for this contribution.
  • AI fully generated the code for this contribution, but I've reviewed and understood it before submitting and will respond without AI during review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Android Controls (HAControls) pipeline to avoid reading friendly_name directly from entity attributes, instead building controls from EntitiesForDisplayManager / EntityDisplay* so names, areas, icons, and other display attributes are resolved consistently (including entity registry data).

Changes:

  • Refactors HaControlsProviderService to source items from EntitiesForDisplayManager (snapshot + observe) and removes registry-handling helpers/structures.
  • Extends EntityDisplay and Entity helpers to expose additional display/control data needed by HAControls (number/media player/cover/vacuum controls, device_class, entity_picture).
  • Migrates individual control implementations to consume EntityDisplayWithContext rather than raw Entity attributes.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
common/src/test/kotlin/io/homeassistant/companion/android/common/data/integration/EntityTest.kt Adds unit tests covering new entity helpers (feature bitmask + domain control extraction + display attributes).
common/src/main/kotlin/io/homeassistant/companion/android/util/RegistriesDataHandler.kt Removes obsolete registry lookup helper previously used by HAControls.
common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/Entity.kt Adds supportsFeature and domain-specific *Controls helpers; restricts friendlyName visibility.
common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/display/EntityDisplay.kt Extends EntityDisplay with additional attributes/controls and wires them from Entity.
common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/display/AlarmDisplay.kt Uses the shared supportsFeature helper instead of direct bitmask casting.
app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControlsProviderService.kt Switches HAControls data source to EntitiesForDisplayManager, adds failed-item display handling, simplifies domain support gating.
app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControlInfo.kt Drops registry area from control metadata (area now comes from EntityDisplayWithContext).
app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControl.kt Uses EntityDisplayWithContext for title/subtitle/state/icon rendering.
app/src/main/kotlin/io/homeassistant/companion/android/controls/CameraControl.kt Uses entityPicture from display item for thumbnails.
app/src/main/kotlin/io/homeassistant/companion/android/controls/ClimateControl.kt Uses resolved climate controls/modes from the display item.
app/src/main/kotlin/io/homeassistant/companion/android/controls/CoverControl.kt Uses resolved cover controls and deviceClass from the display item.
app/src/main/kotlin/io/homeassistant/companion/android/controls/DefaultButtonControl.kt Migrates to EntityDisplayWithContext for templates and domain naming.
app/src/main/kotlin/io/homeassistant/companion/android/controls/DefaultSliderControl.kt Uses numberControls from the display item for range configuration.
app/src/main/kotlin/io/homeassistant/companion/android/controls/DefaultSwitchControl.kt Migrates to EntityDisplayWithContext for templates and domain naming.
app/src/main/kotlin/io/homeassistant/companion/android/controls/FanControl.kt Uses resolved fan controls from the display item.
app/src/main/kotlin/io/homeassistant/companion/android/controls/HaFailedControl.kt Migrates to display item and uses rawState for notfound/exception rendering.
app/src/main/kotlin/io/homeassistant/companion/android/controls/LightControl.kt Uses resolved light controls from the display item.
app/src/main/kotlin/io/homeassistant/companion/android/controls/LockControl.kt Migrates to EntityDisplayWithContext for templates.
app/src/main/kotlin/io/homeassistant/companion/android/controls/MediaPlayerControl.kt Uses resolved media player controls from the display item.
app/src/main/kotlin/io/homeassistant/companion/android/controls/VacuumControl.kt Uses resolved vacuum controls from the display item (but currently retains shared mutable state).
Comments suppressed due to low confidence (2)

app/src/main/kotlin/io/homeassistant/companion/android/controls/VacuumControl.kt:57

  • performAction relies on the mutable supportsTurnOn state from the last rendered control. Use the action's templateId (systemId) to look up the capability and strip the optional server prefix before calling the HA service.
    override suspend fun performAction(integrationRepository: IntegrationRepository, action: ControlAction): Boolean {
        integrationRepository.callAction(
            action.templateId.split(".")[0],
            if (entitySupportsTurnOn) {
                if ((action as? BooleanAction)?.newState == true) "turn_on" else "turn_off"
            } else if ((action as? BooleanAction)?.newState == true) {
                "start"
            } else {
                "return_to_base"
            },
            hashMapOf(
                "entity_id" to action.templateId,
            ),
        )

app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControlsProviderService.kt:353

  • sendControl catches all Exceptions, including CancellationException from a cancelled subscription, and then tries to send a failed control. Rethrow CancellationException so cancellation stops work promptly and doesn't emit extra controls after cancel().
        val control = try {
            domainToHaControl[if (failed) "ha_failed" else item.domain]?.createControl(
                applicationContext,
                item,
                info,
            )
        } catch (e: Exception) {
            Timber.e(e, "Unable to create control for ${item.domain} entity, sending error entity")
            domainToHaControl["ha_failed"]?.createControl(
                applicationContext,
                failedItem(item.entityId, notFound = false),
                info,
            )
        }

Comment on lines 17 to 21
object VacuumControl : HaControl {
private const val SUPPORT_TURN_ON = 1
private var entitySupportedFeatures = 0
private var entitySupportsTurnOn = false

override fun provideControlFeatures(
context: Context,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The issue was there before but it makes sense to fix it.

Comment on lines +65 to 78
val colorTint = when {
item.domain == LIGHT_DOMAIN && item.rawState == "on" -> R.color.colorDeviceControlsLightOn
item.domain == CAMERA_DOMAIN -> R.color.colorDeviceControlsCamera
item.domain == CLIMATE_DOMAIN && item.rawState == "heat"
-> R.color.colorDeviceControlsThermostatHeat

entity.state in listOf(
"off",
"unavailable",
"unknown",
) -> R.color.colorDeviceControlsOff
item.rawState in listOf(
"off",
"unavailable",
"unknown",
) -> R.color.colorDeviceControlsOff

else -> R.color.colorDeviceControlsDefaultOn
}

iconDrawable.setTint(ContextCompat.getColor(context, colorTint))
control.setCustomIcon(iconDrawable.toAndroidIconCompat().toIcon(context))
}
} else {
// Specific override for some domain icons to match HA frontend rather than provided device type
val iconOverride = listOf(MEDIA_PLAYER_DOMAIN, "number")
if (entity.domain in iconOverride) {
val icon = IconicsDrawable(context, entity.getIcon()).apply { sizeDp = 48 }
val tint = if (entity.isActive()) {
R.color.colorDeviceControlsDefaultOn
} else {
R.color.colorDeviceControlsOff
}
icon.setTint(ContextCompat.getColor(context, tint))
control.setCustomIcon(icon.toAndroidIconCompat().toIcon(context))
}
else -> R.color.colorDeviceControlsDefaultOn
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@jpelgrom any opinion on this?

@TimoPtr
TimoPtr marked this pull request as draft July 28, 2026 14:32
@TimoPtr
TimoPtr marked this pull request as ready for review July 29, 2026 14:31
@TimoPtr
TimoPtr requested a review from jpelgrom July 29, 2026 14:31
@TimoPtr TimoPtr linked an issue Jul 29, 2026 that may be closed by this pull request
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.

Stop using friendly_name in HAControl

2 participants