diff --git a/app/src/full/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt b/app/src/full/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt index 03a8a6e06..ba4dbe9a6 100644 --- a/app/src/full/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt +++ b/app/src/full/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt @@ -29,6 +29,7 @@ class LaunchPresenterImpl @Inject constructor( ) ) serverManager.integrationRepository(it.id).getConfig() // Update cached data + serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data } catch (e: Exception) { Log.e(TAG, "Issue updating Registration", e) } diff --git a/app/src/full/java/io/homeassistant/companion/android/matter/MatterManagerImpl.kt b/app/src/full/java/io/homeassistant/companion/android/matter/MatterManagerImpl.kt index df881a78e..86da0080f 100644 --- a/app/src/full/java/io/homeassistant/companion/android/matter/MatterManagerImpl.kt +++ b/app/src/full/java/io/homeassistant/companion/android/matter/MatterManagerImpl.kt @@ -23,7 +23,7 @@ class MatterManagerImpl @Inject constructor( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 override suspend fun coreSupportsCommissioning(serverId: Int): Boolean { - if (!serverManager.isRegistered() || serverManager.getServer(serverId) == null) return false + if (!serverManager.isRegistered() || serverManager.getServer(serverId)?.user?.isAdmin != true) return false val config = serverManager.webSocketRepository(serverId).getConfig() return config != null && config.components.contains("matter") } diff --git a/app/src/full/java/io/homeassistant/companion/android/matter/views/MatterCommissioningView.kt b/app/src/full/java/io/homeassistant/companion/android/matter/views/MatterCommissioningView.kt index 21e852c6f..2f448608f 100644 --- a/app/src/full/java/io/homeassistant/companion/android/matter/views/MatterCommissioningView.kt +++ b/app/src/full/java/io/homeassistant/companion/android/matter/views/MatterCommissioningView.kt @@ -37,6 +37,7 @@ import io.homeassistant.companion.android.R import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.matter.MatterCommissioningViewModel.CommissioningFlowStep import kotlin.math.min import io.homeassistant.companion.android.common.R as commonR @@ -221,7 +222,7 @@ fun PreviewMatterCommissioningView( step = step, deviceName = "Manufacturer Matter Light", servers = listOf( - Server(id = 0, _name = "Home", listOrder = -1, connection = ServerConnectionInfo(externalUrl = ""), session = ServerSessionInfo()) + Server(id = 0, _name = "Home", listOrder = -1, connection = ServerConnectionInfo(externalUrl = ""), session = ServerSessionInfo(), user = ServerUserInfo()) ), onSelectServer = { }, onConfirmCommissioning = { }, diff --git a/app/src/full/java/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt b/app/src/full/java/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt index f6193b400..b7781cd7e 100644 --- a/app/src/full/java/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt +++ b/app/src/full/java/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt @@ -33,7 +33,7 @@ class ThreadManagerImpl @Inject constructor( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 override suspend fun coreSupportsThread(serverId: Int): Boolean { - if (!serverManager.isRegistered() || serverManager.getServer(serverId) == null) return false + if (!serverManager.isRegistered() || serverManager.getServer(serverId)?.user?.isAdmin != true) return false val config = serverManager.webSocketRepository(serverId).getConfig() return config != null && config.components.contains("thread") && diff --git a/app/src/main/java/io/homeassistant/companion/android/launch/LaunchActivity.kt b/app/src/main/java/io/homeassistant/companion/android/launch/LaunchActivity.kt index 3d71c1b21..759d846c5 100644 --- a/app/src/main/java/io/homeassistant/companion/android/launch/LaunchActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/launch/LaunchActivity.kt @@ -21,6 +21,7 @@ import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo import io.homeassistant.companion.android.database.server.ServerType +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.database.settings.WebsocketSetting import io.homeassistant.companion.android.onboarding.OnboardApp import io.homeassistant.companion.android.onboarding.getMessagingToken @@ -149,7 +150,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView { connection = ServerConnectionInfo( externalUrl = formattedUrl ), - session = ServerSessionInfo() + session = ServerSessionInfo(), + user = ServerUserInfo() ) serverId = serverManager.addServer(server) serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode) diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/SettingsPresenterImpl.kt b/app/src/main/java/io/homeassistant/companion/android/settings/SettingsPresenterImpl.kt index e7ff4cb0b..8cf38cc80 100644 --- a/app/src/main/java/io/homeassistant/companion/android/settings/SettingsPresenterImpl.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/SettingsPresenterImpl.kt @@ -13,6 +13,7 @@ import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo import io.homeassistant.companion.android.database.server.ServerType +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.database.settings.SensorUpdateFrequencySetting import io.homeassistant.companion.android.database.settings.Setting import io.homeassistant.companion.android.database.settings.SettingsDao @@ -146,7 +147,8 @@ class SettingsPresenterImpl @Inject constructor( connection = ServerConnectionInfo( externalUrl = formattedUrl ), - session = ServerSessionInfo() + session = ServerSessionInfo(), + user = ServerUserInfo() ) serverId = serverManager.addServer(server) serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode) diff --git a/app/src/minimal/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt b/app/src/minimal/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt index 1a3bb4bef..bcfd9b1ca 100644 --- a/app/src/minimal/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt +++ b/app/src/minimal/java/io/homeassistant/companion/android/launch/LaunchPresenterImpl.kt @@ -22,6 +22,7 @@ class LaunchPresenterImpl @Inject constructor( ) ) serverManager.integrationRepository(it.id).getConfig() // Update cached data + serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data } catch (e: Exception) { Log.e(TAG, "Issue updating Registration", e) } diff --git a/common/schemas/io.homeassistant.companion.android.database.AppDatabase/39.json b/common/schemas/io.homeassistant.companion.android.database.AppDatabase/39.json new file mode 100644 index 000000000..23f7adea9 --- /dev/null +++ b/common/schemas/io.homeassistant.companion.android.database.AppDatabase/39.json @@ -0,0 +1,987 @@ +{ + "formatVersion": 1, + "database": { + "version": 39, + "identityHash": "ae2f0ecc0bd86c23945ff3ff9151339b", + "entities": [ + { + "tableName": "sensor_attributes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))", + "fields": [ + { + "fieldPath": "sensorId", + "columnName": "sensor_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "value_type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sensor_id", + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "authentication_list", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`host`))", + "fields": [ + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "host" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sensors", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL, `registered` INTEGER DEFAULT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT DEFAULT NULL, `last_sent_icon` TEXT DEFAULT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, `state_class` TEXT, `entity_category` TEXT, `core_registration` TEXT, `app_registration` TEXT, PRIMARY KEY(`id`, `server_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registered", + "columnName": "registered", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSentState", + "columnName": "last_sent_state", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "lastSentIcon", + "columnName": "last_sent_icon", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "stateType", + "columnName": "state_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceClass", + "columnName": "device_class", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "unitOfMeasurement", + "columnName": "unit_of_measurement", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "stateClass", + "columnName": "state_class", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "entityCategory", + "columnName": "entity_category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coreRegistration", + "columnName": "core_registration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appRegistration", + "columnName": "app_registration", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "server_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sensor_settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `entries` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))", + "fields": [ + { + "fieldPath": "sensorId", + "columnName": "sensor_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "value_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entries", + "columnName": "entries", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sensor_id", + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "button_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, `require_authentication` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "iconId", + "columnName": "icon_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "service", + "columnName": "service", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceData", + "columnName": "service_data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "requireAuthentication", + "columnName": "require_authentication", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "camera_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "media_player_controls_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `label` TEXT, `show_skip` INTEGER NOT NULL, `show_seek` INTEGER NOT NULL, `show_volume` INTEGER NOT NULL, `show_source` INTEGER NOT NULL DEFAULT false, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "showSkip", + "columnName": "show_skip", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showSeek", + "columnName": "show_seek", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showVolume", + "columnName": "show_volume", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showSource", + "columnName": "show_source", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "static_widget", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `attribute_ids` TEXT, `label` TEXT, `text_size` REAL NOT NULL, `state_separator` TEXT NOT NULL, `attribute_separator` TEXT NOT NULL, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributeIds", + "columnName": "attribute_ids", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "textSize", + "columnName": "text_size", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "stateSeparator", + "columnName": "state_separator", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributeSeparator", + "columnName": "attribute_separator", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdate", + "columnName": "last_update", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "template_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `template` TEXT NOT NULL, `text_size` REAL NOT NULL DEFAULT 12.0, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "template", + "columnName": "template", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "textSize", + "columnName": "text_size", + "affinity": "REAL", + "notNull": true, + "defaultValue": "12.0" + }, + { + "fieldPath": "lastUpdate", + "columnName": "last_update", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notification_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, `source` TEXT NOT NULL, `server_id` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "qs_tiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tile_id` TEXT NOT NULL, `added` INTEGER NOT NULL DEFAULT 1, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER, `entity_id` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT, `should_vibrate` INTEGER NOT NULL DEFAULT 0, `auth_required` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tileId", + "columnName": "tile_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "iconId", + "columnName": "icon_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subtitle", + "columnName": "subtitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shouldVibrate", + "columnName": "should_vibrate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "authRequired", + "columnName": "auth_required", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorites", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorite_cache", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `friendly_name` TEXT NOT NULL, `icon` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "friendlyName", + "columnName": "friendly_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "entity_state_complications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "servers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_name` TEXT NOT NULL, `name_override` TEXT, `_version` TEXT, `list_order` INTEGER NOT NULL, `device_name` TEXT, `external_url` TEXT NOT NULL, `internal_url` TEXT, `cloud_url` TEXT, `webhook_id` TEXT, `secret` TEXT, `cloudhook_url` TEXT, `use_cloud` INTEGER NOT NULL, `internal_ssids` TEXT NOT NULL, `prioritize_internal` INTEGER NOT NULL, `access_token` TEXT, `refresh_token` TEXT, `token_expiration` INTEGER, `token_type` TEXT, `install_id` TEXT, `user_id` TEXT, `user_name` TEXT, `user_is_owner` INTEGER, `user_is_admin` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "_name", + "columnName": "_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "nameOverride", + "columnName": "name_override", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "_version", + "columnName": "_version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "listOrder", + "columnName": "list_order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceName", + "columnName": "device_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.externalUrl", + "columnName": "external_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "connection.internalUrl", + "columnName": "internal_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.cloudUrl", + "columnName": "cloud_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.webhookId", + "columnName": "webhook_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.secret", + "columnName": "secret", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.cloudhookUrl", + "columnName": "cloudhook_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.useCloud", + "columnName": "use_cloud", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "connection.internalSsids", + "columnName": "internal_ssids", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "connection.prioritizeInternal", + "columnName": "prioritize_internal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "session.accessToken", + "columnName": "access_token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.refreshToken", + "columnName": "refresh_token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.tokenExpiration", + "columnName": "token_expiration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "session.tokenType", + "columnName": "token_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.installId", + "columnName": "install_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.id", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.name", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.isOwner", + "columnName": "user_is_owner", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "user.isAdmin", + "columnName": "user_is_admin", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocket_setting` TEXT NOT NULL, `sensor_update_frequency` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "websocketSetting", + "columnName": "websocket_setting", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sensorUpdateFrequency", + "columnName": "sensor_update_frequency", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae2f0ecc0bd86c23945ff3ff9151339b')" + ] + } +} \ No newline at end of file diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/integration/impl/IntegrationRepositoryImpl.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/integration/impl/IntegrationRepositoryImpl.kt index db127f48f..141bb1585 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/data/integration/impl/IntegrationRepositoryImpl.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/integration/impl/IntegrationRepositoryImpl.kt @@ -103,6 +103,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor( ) ) getConfig() // To get version, name, etc stored + webSocketRepository.getCurrentUser() // To get user info stored } catch (e: Exception) { Log.e(TAG, "Unable to save device registration", e) } @@ -583,18 +584,33 @@ class IntegrationRepositoryImpl @AssistedInject constructor( } override suspend fun getEntityUpdates(entityIds: List): Flow>? { - return webSocketRepository.getStateChanges(entityIds) - ?.filter { it.toState != null } - ?.map { - Entity( - it.toState!!.entityId, - it.toState.state, - it.toState.attributes, - it.toState.lastChanged, - it.toState.lastUpdated, - it.toState.context - ) - } + return if (server.user.isAdmin == true) { + webSocketRepository.getStateChanges(entityIds) + ?.filter { it.toState != null } + ?.map { + Entity( + it.toState!!.entityId, + it.toState.state, + it.toState.attributes, + it.toState.lastChanged, + it.toState.lastUpdated, + it.toState.context + ) + } + } else { + webSocketRepository.getStateChanges() + ?.filter { it.newState != null && entityIds.contains(it.entityId) } + ?.map { + Entity( + it.newState!!.entityId, + it.newState.state, + it.newState.attributes, + it.newState.lastChanged, + it.newState.lastUpdated, + it.newState.context + ) + } + } } override suspend fun registerSensor(sensorRegistration: SensorRegistration) { diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt index 504123f99..eb9db317a 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt @@ -7,6 +7,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Ar import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryUpdatedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse +import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryUpdatedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.DomainResponse @@ -25,6 +26,7 @@ interface WebSocketRepository { fun getConnectionState(): WebSocketState? fun shutdown() suspend fun sendPing(): Boolean + suspend fun getCurrentUser(): CurrentUserResponse? suspend fun getConfig(): GetConfigResponse? suspend fun getStates(): List>? suspend fun getAreaRegistry(): List? diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImpl.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImpl.kt index e6ef37453..7d52fda50 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImpl.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/WebSocketRepositoryImpl.kt @@ -25,6 +25,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Ar import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryUpdatedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse +import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryUpdatedEvent import io.homeassistant.companion.android.common.data.websocket.impl.entities.DomainResponse @@ -40,6 +41,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Th import io.homeassistant.companion.android.common.data.websocket.impl.entities.ThreadDatasetTlvResponse import io.homeassistant.companion.android.common.data.websocket.impl.entities.TriggerEvent import io.homeassistant.companion.android.common.util.toHexString +import io.homeassistant.companion.android.database.server.ServerUserInfo import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -130,6 +132,18 @@ class WebSocketRepositoryImpl @AssistedInject constructor( return mapResponse(socketResponse) } + override suspend fun getCurrentUser(): CurrentUserResponse? { + val socketResponse = sendMessage( + mapOf( + "type" to "auth/current_user" + ) + ) + + val response: CurrentUserResponse? = mapResponse(socketResponse) + response?.let { updateServerWithUser(it) } + return response + } + override suspend fun getStates(): List>? { val socketResponse = sendMessage( mapOf( @@ -446,6 +460,25 @@ class WebSocketRepositoryImpl @AssistedInject constructor( return response?.success == true } + /** + * Update this repository's [server] with information from a [CurrentUserResponse] like user + * name and admin status. + */ + private fun updateServerWithUser(user: CurrentUserResponse) { + server?.let { + serverManager.updateServer( + it.copy( + user = ServerUserInfo( + id = user.id, + name = user.name, + isOwner = user.isOwner, + isAdmin = user.isAdmin + ) + ) + ) + } + } + private suspend fun connect(): Boolean { connectedMutex.withLock { if (connection != null && connected.isCompleted) { diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/entities/CurrentUserResponse.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/entities/CurrentUserResponse.kt new file mode 100644 index 000000000..74738832e --- /dev/null +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/websocket/impl/entities/CurrentUserResponse.kt @@ -0,0 +1,11 @@ +package io.homeassistant.companion.android.common.data.websocket.impl.entities + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +data class CurrentUserResponse( + val id: String, + val name: String, + val isOwner: Boolean, + val isAdmin: Boolean +) diff --git a/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt b/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt index 1847a6734..d899275c0 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt @@ -87,7 +87,7 @@ import io.homeassistant.companion.android.common.R as commonR Server::class, Setting::class ], - version = 38, + version = 39, autoMigrations = [ AutoMigration(from = 24, to = 25), AutoMigration(from = 25, to = 26), @@ -102,7 +102,8 @@ import io.homeassistant.companion.android.common.R as commonR AutoMigration(from = 34, to = 35), AutoMigration(from = 35, to = 36), AutoMigration(from = 36, to = 37, spec = AppDatabase.Companion.Migration36to37::class), - AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class) + AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class), + AutoMigration(from = 38, to = 39) ] ) @TypeConverters( diff --git a/common/src/main/java/io/homeassistant/companion/android/database/server/Server.kt b/common/src/main/java/io/homeassistant/companion/android/database/server/Server.kt index ee1d0d651..2e4bb2d10 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/server/Server.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/server/Server.kt @@ -26,10 +26,11 @@ data class Server( @ColumnInfo(name = "device_name") val deviceName: String? = null, @Embedded val connection: ServerConnectionInfo, - @Embedded val session: ServerSessionInfo + @Embedded val session: ServerSessionInfo, + @Embedded val user: ServerUserInfo ) { - constructor(id: Int, _name: String, nameOverride: String?, _version: String?, listOrder: Int, deviceName: String?, connection: ServerConnectionInfo, session: ServerSessionInfo) : - this(id, _name, nameOverride, _version, ServerType.DEFAULT, listOrder, deviceName, connection, session) + constructor(id: Int, _name: String, nameOverride: String?, _version: String?, listOrder: Int, deviceName: String?, connection: ServerConnectionInfo, session: ServerSessionInfo, user: ServerUserInfo) : + this(id, _name, nameOverride, _version, ServerType.DEFAULT, listOrder, deviceName, connection, session, user) val friendlyName: String get() = nameOverride ?: _name.ifBlank { connection.externalUrl } diff --git a/common/src/main/java/io/homeassistant/companion/android/database/server/ServerUserInfo.kt b/common/src/main/java/io/homeassistant/companion/android/database/server/ServerUserInfo.kt new file mode 100644 index 000000000..a52926505 --- /dev/null +++ b/common/src/main/java/io/homeassistant/companion/android/database/server/ServerUserInfo.kt @@ -0,0 +1,14 @@ +package io.homeassistant.companion.android.database.server + +import androidx.room.ColumnInfo + +data class ServerUserInfo( + @ColumnInfo(name = "user_id") + val id: String? = null, + @ColumnInfo(name = "user_name") + val name: String? = null, + @ColumnInfo(name = "user_is_owner") + val isOwner: Boolean? = null, + @ColumnInfo(name = "user_is_admin") + val isAdmin: Boolean? = null +) diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/HomePresenterImpl.kt b/wear/src/main/java/io/homeassistant/companion/android/home/HomePresenterImpl.kt index d231d5f3a..316014060 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/HomePresenterImpl.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/HomePresenterImpl.kt @@ -192,6 +192,7 @@ class HomePresenterImpl @Inject constructor( false ) ) + serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data } catch (e: Exception) { Log.e(TAG, "Issue updating Registration", e) } diff --git a/wear/src/main/java/io/homeassistant/companion/android/onboarding/OnboardingPresenterImpl.kt b/wear/src/main/java/io/homeassistant/companion/android/onboarding/OnboardingPresenterImpl.kt index e68124d02..7e4813bd0 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/onboarding/OnboardingPresenterImpl.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/onboarding/OnboardingPresenterImpl.kt @@ -18,6 +18,7 @@ import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo import io.homeassistant.companion.android.database.server.ServerType +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.util.UrlUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -143,7 +144,8 @@ class OnboardingPresenterImpl @Inject constructor( connection = ServerConnectionInfo( externalUrl = formattedUrl ), - session = ServerSessionInfo() + session = ServerSessionInfo(), + user = ServerUserInfo() ) serverId = serverManager.addServer(server) serverManager.authenticationRepository(serverId).registerAuthorizationCode(code) diff --git a/wear/src/main/java/io/homeassistant/companion/android/onboarding/manual/ManualSetupPresenterImpl.kt b/wear/src/main/java/io/homeassistant/companion/android/onboarding/manual/ManualSetupPresenterImpl.kt index 13ade7386..6827b7bdf 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/onboarding/manual/ManualSetupPresenterImpl.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/onboarding/manual/ManualSetupPresenterImpl.kt @@ -14,6 +14,7 @@ import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo import io.homeassistant.companion.android.database.server.ServerType +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.util.UrlUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -102,7 +103,8 @@ class ManualSetupPresenterImpl @Inject constructor( connection = ServerConnectionInfo( externalUrl = formattedUrl ), - session = ServerSessionInfo() + session = ServerSessionInfo(), + user = ServerUserInfo() ) serverId = serverManager.addServer(server) serverManager.authenticationRepository(serverId).registerAuthorizationCode(code) diff --git a/wear/src/main/java/io/homeassistant/companion/android/phone/PhoneSettingsListener.kt b/wear/src/main/java/io/homeassistant/companion/android/phone/PhoneSettingsListener.kt index 45b2168ab..c67676e24 100755 --- a/wear/src/main/java/io/homeassistant/companion/android/phone/PhoneSettingsListener.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/phone/PhoneSettingsListener.kt @@ -22,6 +22,7 @@ import io.homeassistant.companion.android.database.server.Server import io.homeassistant.companion.android.database.server.ServerConnectionInfo import io.homeassistant.companion.android.database.server.ServerSessionInfo import io.homeassistant.companion.android.database.server.ServerType +import io.homeassistant.companion.android.database.server.ServerUserInfo import io.homeassistant.companion.android.database.wear.FavoritesDao import io.homeassistant.companion.android.database.wear.getAll import io.homeassistant.companion.android.database.wear.replaceAll @@ -129,7 +130,8 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange connection = ServerConnectionInfo( externalUrl = formattedUrl ), - session = ServerSessionInfo() + session = ServerSessionInfo(), + user = ServerUserInfo() ) serverId = serverManager.addServer(server) serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode)