Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.homeassistant.companion.android.util.vehicle

import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.supportsAlarmControlPanelArmAway

val SUPPORTED_DOMAINS_WITH_STRING = mapOf(
"alarm_control_panel" to R.string.alarm_control_panels,
Expand Down Expand Up @@ -45,9 +44,3 @@ fun canNavigate(entity: Entity): Boolean {
((entity.attributes["longitude"] as? Number)?.toDouble() != null)
)
}

fun alarmHasNoCode(entity: Entity): Boolean {
return entity.domain == "alarm_control_panel" &&
entity.attributes["code_format"] as? String == null &&
entity.supportsAlarmControlPanelArmAway()
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.homeassistant.companion.android.common.data.integration.friendlyName
import io.homeassistant.companion.android.common.data.integration.friendlyState
import io.homeassistant.companion.android.common.data.integration.getIcon
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.integration.isAlarmActionable
import io.homeassistant.companion.android.common.data.integration.isExecuting
import io.homeassistant.companion.android.common.data.integration.onPressed
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
Expand All @@ -35,7 +36,6 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.En
import io.homeassistant.companion.android.util.vehicle.MAP_DOMAINS
import io.homeassistant.companion.android.util.vehicle.NOT_ACTIONABLE_DOMAINS
import io.homeassistant.companion.android.util.vehicle.SUPPORTED_DOMAINS
import io.homeassistant.companion.android.util.vehicle.alarmHasNoCode
import io.homeassistant.companion.android.util.vehicle.canNavigate
import io.homeassistant.companion.android.util.vehicle.getChangeServerGridItem
import io.homeassistant.companion.android.util.vehicle.getDomainList
Expand Down Expand Up @@ -174,7 +174,7 @@ class EntityGridVehicleScreen(
if (entity.isExecuting()) {
gridItem.setLoading(entity.isExecuting())
} else {
if (entity.domain !in NOT_ACTIONABLE_DOMAINS || canNavigate(entity) || alarmHasNoCode(entity)) {
if (entity.domain !in NOT_ACTIONABLE_DOMAINS || canNavigate(entity) || entity.isAlarmActionable()) {
gridItem
.setOnClickListener {
Timber.i("${entity.entityId} clicked")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.homeassistant.companion.android.common.data.integration

import androidx.annotation.VisibleForTesting
import timber.log.Timber
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.ALARM_CONTROL_PANEL_DOMAIN

@VisibleForTesting
internal const val ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY = 2

private fun Entity.isAlarmControlPanelEntity(): Boolean {
return domain == ALARM_CONTROL_PANEL_DOMAIN
}

private fun Entity.alarmHasNoCode(): Boolean {
return isAlarmControlPanelEntity() && (attributes["code_format"] as? String)?.isNotEmpty() != true
Comment thread
poupounetjoyeux marked this conversation as resolved.
}

private fun Entity.alarmCanBeArmedWithoutCode(): Boolean {
return isAlarmControlPanelEntity() && attributes["code_arm_required"] as? Boolean == false
}

private fun Entity.supportsAlarmControlPanelArmAway(): Boolean {
return try {
if (!isAlarmControlPanelEntity()) {
return false
}

(attributes["supported_features"] as Number).toInt() and
ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY == ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY
} catch (e: Exception) {
Timber.tag(EntityExt.TAG).e(e, "Unable to get supportsArmedAway")
false
}
}

private fun Entity.alarmIsDisarmed(): Boolean {
return isAlarmControlPanelEntity() && state == "disarmed"
}

private fun Entity.alarmCanBeArmedAway(): Boolean {
if(!isAlarmControlPanelEntity()) {
return false
}

if (!alarmIsDisarmed() || !supportsAlarmControlPanelArmAway()) {
return false
}

return alarmHasNoCode() || alarmCanBeArmedWithoutCode()
}

private fun Entity.alarmCanBeDisarmed(): Boolean {
return isAlarmControlPanelEntity() && !alarmIsDisarmed() && alarmHasNoCode()
}

fun Entity.isAlarmActionable(): Boolean {
return isAlarmControlPanelEntity() && alarmCanBeDisarmed() || alarmCanBeArmedAway()
}

fun Entity.getAlarmOnPressedAction(): String? {
if(!isAlarmControlPanelEntity()) {
return null
}

if (alarmCanBeDisarmed()) {
return "alarm_disarm"
}

if (alarmCanBeArmedAway()) {
return "alarm_arm_away"
}

return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ object EntityExt {
val LIGHT_MODE_NO_BRIGHTNESS_SUPPORT = listOf("unknown", "onoff")
const val LIGHT_SUPPORT_BRIGHTNESS_DEPR = 1
const val LIGHT_SUPPORT_COLOR_TEMP_DEPR = 2
const val ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY = 2
const val MEDIA_PLAYER_SUPPORT_VOLUME_SET = 4

val DOMAINS_PRESS = listOf("button", "input_button")
Expand Down Expand Up @@ -170,17 +169,6 @@ fun Entity.getCoverPosition(): EntityPosition? {
}
}

fun Entity.supportsAlarmControlPanelArmAway(): Boolean {
return try {
if (domain != "alarm_control_panel") return false
(attributes["supported_features"] as Number).toInt() and
EntityExt.ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY == EntityExt.ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY
} catch (e: Exception) {
Timber.tag(EntityExt.TAG).e(e, "Unable to get supportsArmedAway")
false
}
}

fun Entity.supportsFanSetSpeed(): Boolean {
return try {
if (domain != "fan") return false
Expand Down Expand Up @@ -808,9 +796,7 @@ suspend fun Entity.onPressed(integrationRepository: IntegrationRepository) {
if (state == "unlocked") "lock" else "unlock"
}

"alarm_control_panel" -> {
if (state != "disarmed") "alarm_disarm" else "alarm_arm_away"
}
"alarm_control_panel" -> getAlarmOnPressedAction()

in EntityExt.DOMAINS_PRESS -> "press"
"fan",
Expand All @@ -825,6 +811,11 @@ suspend fun Entity.onPressed(integrationRepository: IntegrationRepository) {
else -> "toggle"
}

if (action == null) {
Timber.tag(EntityExt.TAG).w("No action called when entity '%s' was pressed", entityId)
return
}

integrationRepository.callAction(
domain = this.domain,
action = action,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ object IntegrationDomains {
const val MEDIA_PLAYER_DOMAIN = "media_player"
const val IMAGE_DOMAIN = "image"
const val TODO_DOMAIN = "todo"

const val ALARM_CONTROL_PANEL_DOMAIN = "alarm_control_panel"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.homeassistant.companion.android.common.data.integration

import java.time.LocalDateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue

class AlarmControlPanelEntityExtTest {
Comment thread
poupounetjoyeux marked this conversation as resolved.

@Test
fun `Given disarmed alarm without code supporting arm_away When pressed Then alarm is actionable and action is alarm_arm_away`() {
val alarmEntity = createAlarmEntity("", requiredArmCode = false, supportArmAway = true, isArmed = false)

assertTrue(alarmEntity.isAlarmActionable())
assertEquals("alarm_arm_away", alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given disarmed alarm with not required arm code supporting arm_away When pressed Then alarm is actionable and action is alarm_arm_away`() {
val alarmEntity = createAlarmEntity("A_C0DE", requiredArmCode = false, supportArmAway = true, isArmed = false)

assertTrue(alarmEntity.isAlarmActionable())
assertEquals("alarm_arm_away", alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given armed alarm without code When pressed Then alarm is actionable and action is alarm_disarm`() {
val alarmEntity = createAlarmEntity("", requiredArmCode = false, supportArmAway = false, isArmed = true)

assertTrue(alarmEntity.isAlarmActionable())
assertEquals("alarm_disarm", alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given disarmed alarm without code not supporting arm_away When pressed Then alarm is not actionable and action is null`() {
val alarmEntity = createAlarmEntity("", requiredArmCode = false, supportArmAway = false, isArmed = false)

assertFalse(alarmEntity.isAlarmActionable())
assertEquals(null, alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given disarmed alarm with required arm code supporting arm_away When pressed Then alarm is not actionable and action is null`() {
val alarmEntity = createAlarmEntity("A_C0DE", requiredArmCode = true, supportArmAway = true, isArmed = false)

assertFalse(alarmEntity.isAlarmActionable())
assertEquals(null, alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given armed alarm with code When pressed Then alarm is not actionable and action is null`() {
val alarmEntity = createAlarmEntity("A_C0DE", requiredArmCode = false, supportArmAway = true, isArmed = true)

assertFalse(alarmEntity.isAlarmActionable())
assertEquals(null, alarmEntity.getAlarmOnPressedAction())
}

@Test
fun `Given not an alarm entity When pressed Then alarm is not actionable and action is null`() {
val otherEntity = Entity("other_domain.an_entity_id", "", mapOf(), LocalDateTime.now(), LocalDateTime.now())
assertFalse(otherEntity.isAlarmActionable())
assertEquals(null, otherEntity.getAlarmOnPressedAction())
}

private fun createAlarmEntity(code: String, requiredArmCode: Boolean, supportArmAway: Boolean, isArmed: Boolean): Entity {
val state = if (isArmed) "armed_away" else "disarmed"

val attributes = mutableMapOf<String, Any?>()
attributes["code_format"] = if (code.isEmpty()) null else "text"
attributes["code_arm_required"] = if (code.isEmpty()) false else requiredArmCode
attributes["supported_features"] = if (supportArmAway) ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY + 4 else 4
return Entity("alarm_control_panel.an_alarm_id", state, attributes, LocalDateTime.now(), LocalDateTime.now())
}
}