Add 'toggle' tap action to entity state widget (#3798)

* [WIP] Widget tap action: toggle entity

* Add feedback on press and failure

* Share code for pressing on entities

* Align cover press action

 - Toggle will stop if possible when opening/closing if supported so prefer toggle instead of open/close

* Toggle by default if supported

 - Set the default tap action for supported entities to toggle instead of refresh

* Update widget description
This commit is contained in:
Joris Pelgröm 2023-08-19 21:24:44 +02:00 committed by GitHub
parent e3ce9ed5b0
commit 7f14582909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1210 additions and 69 deletions

View File

@ -22,7 +22,9 @@ import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.integration.getIcon
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.database.qs.TileDao
import io.homeassistant.companion.android.database.qs.TileEntity
@ -236,22 +238,9 @@ abstract class TileExtensions : TileService() {
}
withContext(Dispatchers.IO) {
try {
serverManager.integrationRepository(tileData.serverId).callService(
tileData.entityId.split(".")[0],
when (tileData.entityId.split(".")[0]) {
"button", "input_button" -> "press"
in toggleDomains -> "toggle"
"lock" -> {
val state = serverManager.integrationRepository(tileData.serverId).getEntity(tileData.entityId)
if (state?.state == "locked") {
"unlock"
} else {
"lock"
}
}
else -> "turn_on"
},
hashMapOf("entity_id" to tileData.entityId)
onEntityPressedWithoutState(
tileData.entityId,
serverManager.integrationRepository(tileData.serverId)
)
Log.d(TAG, "Service call sent for tile ID: $tileId")
} catch (e: Exception) {
@ -342,11 +331,7 @@ abstract class TileExtensions : TileService() {
companion object {
private const val TAG = "TileExtensions"
private val toggleDomains = listOf(
"automation", "cover", "fan", "humidifier", "input_boolean", "light",
"media_player", "remote", "siren", "switch"
)
private val toggleDomainsWithLock = toggleDomains.plus("lock")
private val toggleDomainsWithLock = EntityExt.DOMAINS_TOGGLE
private val validActiveStates = listOf("on", "open", "locked")
}

View File

@ -15,6 +15,7 @@ import androidx.fragment.app.viewModels
import com.google.accompanist.themeadapter.material.MdcTheme
import com.mikepenz.iconics.typeface.IIcon
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.settings.addHelpMenuProvider
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
import io.homeassistant.companion.android.util.icondialog.IconDialog
@ -25,10 +26,7 @@ class ManageTilesFragment : Fragment() {
companion object {
private const val TAG = "TileFragment"
val validDomains = listOf(
"automation", "button", "cover", "fan", "humidifier", "input_boolean", "input_button", "light",
"lock", "media_player", "remote", "siren", "scene", "script", "switch"
)
val validDomains = EntityExt.APP_PRESS_ACTION_DOMAINS
}
val viewModel: ManageTilesViewModel by viewModels()

View File

@ -12,6 +12,7 @@ import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.RemoteViews
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import com.google.android.material.color.DynamicColors
@ -20,9 +21,11 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.canSupportPrecision
import io.homeassistant.companion.android.common.data.integration.friendlyState
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
import io.homeassistant.companion.android.database.widget.StaticWidgetEntity
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
import io.homeassistant.companion.android.database.widget.WidgetTapAction
import io.homeassistant.companion.android.util.getAttribute
import io.homeassistant.companion.android.widgets.BaseWidgetProvider
import kotlinx.coroutines.launch
@ -34,6 +37,8 @@ class EntityWidget : BaseWidgetProvider() {
companion object {
private const val TAG = "StaticWidget"
internal const val TOGGLE_ENTITY =
"io.homeassistant.companion.android.widgets.entity.EntityWidget.TOGGLE_ENTITY"
internal const val EXTRA_SERVER_ID = "EXTRA_SERVER_ID"
internal const val EXTRA_ENTITY_ID = "EXTRA_ENTITY_ID"
@ -42,6 +47,7 @@ class EntityWidget : BaseWidgetProvider() {
internal const val EXTRA_TEXT_SIZE = "EXTRA_TEXT_SIZE"
internal const val EXTRA_STATE_SEPARATOR = "EXTRA_STATE_SEPARATOR"
internal const val EXTRA_ATTRIBUTE_SEPARATOR = "EXTRA_ATTRIBUTE_SEPARATOR"
internal const val EXTRA_TAP_ACTION = "EXTRA_TAP_ACTION"
internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE"
internal const val EXTRA_TEXT_COLOR = "EXTRA_TEXT_COLOR"
@ -55,12 +61,13 @@ class EntityWidget : BaseWidgetProvider() {
ComponentName(context, EntityWidget::class.java)
override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity<Map<String, Any>>?): RemoteViews {
val widget = staticWidgetDao.get(appWidgetId)
val intent = Intent(context, EntityWidget::class.java).apply {
action = UPDATE_VIEW
action = if (widget?.tapAction == WidgetTapAction.TOGGLE) TOGGLE_ENTITY else UPDATE_VIEW
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val widget = staticWidgetDao.get(appWidgetId)
val useDynamicColors = widget?.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable()
val views = RemoteViews(context.packageName, if (useDynamicColors) R.layout.widget_static_wrapper_dynamiccolor else R.layout.widget_static_wrapper_default).apply {
if (widget != null) {
@ -83,6 +90,14 @@ class EntityWidget : BaseWidgetProvider() {
}
// Content
setViewVisibility(
R.id.widgetTextLayout,
View.VISIBLE
)
setViewVisibility(
R.id.widgetProgressBar,
View.INVISIBLE
)
val resolvedText = resolveTextToShow(
context,
serverId,
@ -194,6 +209,12 @@ class EntityWidget : BaseWidgetProvider() {
val textSizeSelection: String? = extras.getString(EXTRA_TEXT_SIZE)
val stateSeparatorSelection: String? = extras.getString(EXTRA_STATE_SEPARATOR)
val attributeSeparatorSelection: String? = extras.getString(EXTRA_ATTRIBUTE_SEPARATOR)
val tapActionSelection: WidgetTapAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
extras.getSerializable(EXTRA_TAP_ACTION, WidgetTapAction::class.java)
} else {
@Suppress("DEPRECATION")
extras.getSerializable(EXTRA_TAP_ACTION) as? WidgetTapAction
} ?: WidgetTapAction.REFRESH
val backgroundTypeSelection: WidgetBackgroundType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
extras.getSerializable(EXTRA_BACKGROUND_TYPE, WidgetBackgroundType::class.java)
} else {
@ -224,6 +245,7 @@ class EntityWidget : BaseWidgetProvider() {
textSizeSelection?.toFloatOrNull() ?: 30F,
stateSeparatorSelection ?: "",
attributeSeparatorSelection ?: "",
tapActionSelection,
staticWidgetDao.get(appWidgetId)?.lastUpdate ?: "",
backgroundTypeSelection,
textColorSelection
@ -241,6 +263,45 @@ class EntityWidget : BaseWidgetProvider() {
}
}
private fun toggleEntity(context: Context, appWidgetId: Int) {
widgetScope?.launch {
// Show progress bar as feedback
val appWidgetManager = AppWidgetManager.getInstance(context)
val loadingViews = RemoteViews(context.packageName, R.layout.widget_static)
loadingViews.setViewVisibility(R.id.widgetProgressBar, View.VISIBLE)
loadingViews.setViewVisibility(R.id.widgetTextLayout, View.GONE)
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, loadingViews)
var success = false
staticWidgetDao.get(appWidgetId)?.let {
try {
onEntityPressedWithoutState(
it.entityId,
serverManager.integrationRepository(it.serverId)
)
success = true
} catch (e: Exception) {
Log.e(TAG, "Unable to send toggle service call", e)
}
}
if (!success) {
Toast.makeText(context, commonR.string.service_call_failure, Toast.LENGTH_LONG).show()
val views = getWidgetRemoteViews(context, appWidgetId)
appWidgetManager.updateAppWidget(appWidgetId, views)
} // else update will be triggered by websocket subscription
}
}
override fun onReceive(context: Context, intent: Intent) {
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
super.onReceive(context, intent)
when (lastIntent) {
TOGGLE_ENTITY -> toggleEntity(context, appWidgetId)
}
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
widgetScope?.launch {
staticWidgetDao.deleteAll(appWidgetIds)

View File

@ -23,8 +23,11 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
import io.homeassistant.companion.android.database.widget.WidgetTapAction
import io.homeassistant.companion.android.databinding.WidgetStaticConfigureBinding
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel
import io.homeassistant.companion.android.util.getHexForColor
@ -122,6 +125,9 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
val staticWidget = staticWidgetDao.get(appWidgetId)
val tapActionValues = listOf(getString(commonR.string.widget_tap_action_toggle), getString(commonR.string.refresh))
binding.tapActionList.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, tapActionValues)
val backgroundTypeValues = mutableListOf(
getString(commonR.string.widget_background_type_daynight),
getString(commonR.string.widget_background_type_transparent)
@ -162,6 +168,10 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
setupAttributes()
}
val toggleable = entity?.domain in EntityExt.APP_PRESS_ACTION_DOMAINS
binding.tapAction.isVisible = toggleable
binding.tapActionList.setSelection(if (toggleable && staticWidget.tapAction == WidgetTapAction.TOGGLE) 0 else 1)
binding.backgroundType.setSelection(
when {
staticWidget.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable() ->
@ -272,6 +282,9 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
attributesAdapter.addAll(*fetchedAttributes?.keys.orEmpty().toTypedArray())
binding.widgetTextConfigAttribute.setTokenizer(CommaTokenizer())
runOnUiThread {
val toggleable = selectedEntity?.domain in EntityExt.APP_PRESS_ACTION_DOMAINS
binding.tapAction.isVisible = toggleable
binding.tapActionList.setSelection(if (toggleable) 0 else 1)
attributesAdapter.notifyDataSetChanged()
}
}
@ -342,6 +355,14 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
)
}
intent.putExtra(
EntityWidget.EXTRA_TAP_ACTION,
when (binding.tapActionList.selectedItemPosition) {
0 -> WidgetTapAction.TOGGLE
else -> WidgetTapAction.REFRESH
}
)
intent.putExtra(
EntityWidget.EXTRA_BACKGROUND_TYPE,
when (binding.backgroundType.selectedItem as String?) {

View File

@ -56,4 +56,14 @@
android:maxLines="2" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/widgetProgressBar"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="invisible"
android:indeterminate="true"
android:indeterminateTint="@color/colorAccent"
android:layout_gravity="center"
/>
</FrameLayout>

View File

@ -196,6 +196,28 @@
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:id="@+id/tap_action"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:text="@string/widget_tap_action_label" />
<Spinner
android:id="@+id/tap_action_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,17 @@ object EntityExt {
const val LIGHT_SUPPORT_BRIGHTNESS_DEPR = 1
const val LIGHT_SUPPORT_COLOR_TEMP_DEPR = 2
const val ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY = 2
val DOMAINS_PRESS = listOf("button", "input_button")
val DOMAINS_TOGGLE = listOf(
"automation", "cover", "fan", "humidifier", "input_boolean", "light", "lock",
"media_player", "remote", "siren", "switch"
)
val APP_PRESS_ACTION_DOMAINS = DOMAINS_PRESS + DOMAINS_TOGGLE + listOf(
"scene",
"script"
)
}
val <T> Entity<T>.domain: String
@ -601,14 +612,10 @@ suspend fun <T> Entity<T>.onPressed(
"lock" -> {
if (state == "unlocked") "lock" else "unlock"
}
"cover" -> {
if (state == "open") "close_cover" else "open_cover"
}
"alarm_control_panel" -> {
if (state != "disarmed") "alarm_disarm" else "alarm_arm_away"
}
"button",
"input_button" -> "press"
in EntityExt.DOMAINS_PRESS -> "press"
"fan",
"input_boolean",
"script",
@ -626,6 +633,36 @@ suspend fun <T> Entity<T>.onPressed(
)
}
/**
* Execute an app press action like [Entity.onPressed], but without a current state if possible to
* speed up the execution.
* @throws IntegrationException on network errors
*/
suspend fun onEntityPressedWithoutState(
entityId: String,
integrationRepository: IntegrationRepository
) {
val domain = entityId.split(".")[0]
val service = when (domain) {
"lock" -> {
val lockEntity = try {
integrationRepository.getEntity(entityId)
} catch (e: Exception) {
null
}
if (lockEntity?.state == "locked") "unlock" else "lock"
}
in EntityExt.DOMAINS_PRESS -> "press"
in EntityExt.DOMAINS_TOGGLE -> "toggle"
else -> "turn_on"
}
integrationRepository.callService(
domain = domain,
service = service,
serviceData = hashMapOf("entity_id" to entityId)
)
}
val <T> Entity<T>.friendlyName: String
get() = (attributes as? Map<*, *>)?.get("friendly_name")?.toString() ?: entityId

View File

@ -67,6 +67,7 @@ import io.homeassistant.companion.android.database.widget.StaticWidgetEntity
import io.homeassistant.companion.android.database.widget.TemplateWidgetDao
import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity
import io.homeassistant.companion.android.database.widget.WidgetBackgroundTypeConverter
import io.homeassistant.companion.android.database.widget.WidgetTapActionConverter
import kotlinx.coroutines.runBlocking
import java.util.UUID
import io.homeassistant.companion.android.common.R as commonR
@ -90,7 +91,7 @@ import io.homeassistant.companion.android.common.R as commonR
Server::class,
Setting::class
],
version = 42,
version = 43,
autoMigrations = [
AutoMigration(from = 24, to = 25),
AutoMigration(from = 25, to = 26),
@ -108,7 +109,8 @@ import io.homeassistant.companion.android.common.R as commonR
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class),
AutoMigration(from = 38, to = 39),
AutoMigration(from = 39, to = 40),
AutoMigration(from = 41, to = 42)
AutoMigration(from = 41, to = 42),
AutoMigration(from = 42, to = 43)
]
)
@TypeConverters(
@ -116,7 +118,8 @@ import io.homeassistant.companion.android.common.R as commonR
LocalSensorSettingConverter::class,
EntriesTypeConverter::class,
SensorSettingTypeConverter::class,
WidgetBackgroundTypeConverter::class
WidgetBackgroundTypeConverter::class,
WidgetTapActionConverter::class
)
abstract class AppDatabase : RoomDatabase() {
abstract fun authenticationDao(): AuthenticationDao

View File

@ -22,6 +22,8 @@ data class StaticWidgetEntity(
val stateSeparator: String = "",
@ColumnInfo(name = "attribute_separator")
val attributeSeparator: String = "",
@ColumnInfo(name = "tap_action", defaultValue = "REFRESH")
val tapAction: WidgetTapAction,
@ColumnInfo(name = "last_update")
val lastUpdate: String,
@ColumnInfo(name = "background_type", defaultValue = "DAYNIGHT")

View File

@ -0,0 +1,16 @@
package io.homeassistant.companion.android.database.widget
import androidx.room.TypeConverter
enum class WidgetTapAction {
REFRESH, TOGGLE
}
class WidgetTapActionConverter {
@TypeConverter
fun toWidgetTapAction(setting: String): WidgetTapAction = WidgetTapAction.valueOf(setting)
@TypeConverter
fun fromWidgetBackgroundType(setting: WidgetTapAction): String = setting.name
}

View File

@ -224,7 +224,7 @@
<string name="entity_attribute_checkbox">Append attribute value</string>
<string name="entity_id">Entity ID</string>
<string name="entity_id_name">Entity ID: %1$s</string>
<string name="entity_widget_desc">Current state and attribute of any entity</string>
<string name="entity_widget_desc">View any entity\'s current state and toggle it</string>
<string name="error_auth_revoked">It appears that your authorization was revoked, please reconnect to Home Assistant.</string>
<string name="error_connection_failed">Unable to connect to Home Assistant.</string>
<string name="error_loading_entities">Error loading entities</string>
@ -956,6 +956,8 @@
<string name="widget_spinner_server">Server:</string>
<string name="widget_state_separator_label">State and attribute separator:</string>
<string name="widget_static_image_description">Entity state</string>
<string name="widget_tap_action_label">Tap action:</string>
<string name="widget_tap_action_toggle">Toggle</string>
<string name="widget_template_error">Unable to render the template</string>
<string name="widget_text_hint_label">Label</string>
<string name="widget_text_hint_service_data">Entity ID</string>

View File

@ -5,6 +5,7 @@ import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.common.data.authentication.SessionState
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
@ -31,10 +32,6 @@ class HomePresenterImpl @Inject constructor(
) : HomePresenter {
companion object {
val toggleDomains = listOf(
"cover", "fan", "humidifier", "input_boolean", "light", "lock",
"media_player", "remote", "siren", "switch"
)
val domainsWithNames = mapOf(
"button" to commonR.string.buttons,
"cover" to commonR.string.covers,
@ -97,7 +94,7 @@ class HomePresenterImpl @Inject constructor(
"lock"
}
}
in toggleDomains -> "toggle"
in EntityExt.DOMAINS_TOGGLE -> "toggle"
else -> "turn_on"
}
try {

View File

@ -41,7 +41,6 @@ import io.homeassistant.companion.android.common.data.integration.getLightBright
import io.homeassistant.companion.android.common.data.integration.supportsFanSetSpeed
import io.homeassistant.companion.android.common.data.integration.supportsLightBrightness
import io.homeassistant.companion.android.common.data.integration.supportsLightColorTemperature
import io.homeassistant.companion.android.home.HomePresenterImpl
import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.util.getColorTemperature
import io.homeassistant.companion.android.util.onEntityClickedFeedback
@ -84,7 +83,7 @@ fun DetailsPanelView(
val friendlyName = attributes["friendly_name"].toString()
Text(friendlyName)
if (entity.domain in HomePresenterImpl.toggleDomains) {
if (entity.domain in EntityExt.DOMAINS_TOGGLE) {
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
ToggleButton(
checked = isChecked,

View File

@ -23,9 +23,9 @@ import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.integration.getIcon
import io.homeassistant.companion.android.home.HomePresenterImpl
import io.homeassistant.companion.android.theme.wearColorPalette
import io.homeassistant.companion.android.util.WearToggleChip
import io.homeassistant.companion.android.util.onEntityClickedFeedback
@ -46,7 +46,7 @@ fun EntityUi(
val iconBitmap = entity.getIcon(LocalContext.current)
val friendlyName = attributes["friendly_name"].toString()
if (entity.domain in HomePresenterImpl.toggleDomains) {
if (entity.domain in EntityExt.DOMAINS_TOGGLE) {
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
ToggleChip(
checked = isChecked,

View File

@ -10,9 +10,9 @@ import android.os.VibratorManager
import android.util.Log
import androidx.core.content.getSystemService
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.home.HomePresenterImpl
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
@ -46,30 +46,10 @@ class TileActionReceiver : BroadcastReceiver() {
}
}
val domain = entityId.split(".")[0]
val serviceName = when (domain) {
"button", "input_button" -> "press"
"lock" -> {
val lockEntity = try {
serverManager.integrationRepository().getEntity(entityId)
} catch (e: Exception) {
null
}
if (lockEntity?.state == "locked") {
"unlock"
} else {
"lock"
}
}
in HomePresenterImpl.toggleDomains -> "toggle"
else -> "turn_on"
}
try {
serverManager.integrationRepository().callService(
domain,
serviceName,
hashMapOf("entity_id" to entityId)
onEntityPressedWithoutState(
entityId = entityId,
integrationRepository = serverManager.integrationRepository()
)
} catch (e: Exception) {
Log.e(TAG, "Cannot call tile service", e)