Add authentication to widget buttons (#2798)

This commit is contained in:
andyboeh 2022-09-05 15:50:53 +02:00 committed by GitHub
parent 84aa4454b7
commit 6b06e0d8a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 825 additions and 7 deletions

View file

@ -99,6 +99,7 @@
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.CALL_SERVICE" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.CALL_SERVICE_AUTH" />
<action android:name="io.homeassistant.companion.android.widgets.ButtonWidget.RECEIVE_DATA" />
</intent-filter>
@ -176,6 +177,11 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity android:name=".widgets.common.WidgetAuthenticationActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/Theme.Transparent">
</activity>
<activity android:name=".widgets.camera.CameraWidgetConfigureActivity"
android:configChanges="orientation|screenSize"
android:exported="true">

View file

@ -31,6 +31,7 @@ import io.homeassistant.companion.android.database.widget.ButtonWidgetDao
import io.homeassistant.companion.android.database.widget.ButtonWidgetEntity
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
import io.homeassistant.companion.android.util.getAttribute
import io.homeassistant.companion.android.widgets.common.WidgetAuthenticationActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -43,8 +44,10 @@ import io.homeassistant.companion.android.common.R as commonR
class ButtonWidget : AppWidgetProvider() {
companion object {
private const val TAG = "ButtonWidget"
private const val CALL_SERVICE =
public const val CALL_SERVICE =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.CALL_SERVICE"
private const val CALL_SERVICE_AUTH =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.CALL_SERVICE_AUTH"
internal const val RECEIVE_DATA =
"io.homeassistant.companion.android.widgets.button.ButtonWidget.RECEIVE_DATA"
@ -55,6 +58,7 @@ class ButtonWidget : AppWidgetProvider() {
internal const val EXTRA_ICON = "EXTRA_ICON"
internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE"
internal const val EXTRA_TEXT_COLOR = "EXTRA_TEXT_COLOR"
internal const val EXTRA_REQUIRE_AUTHENTICATION = "EXTRA_REQUIRE_AUTHENTICATION"
// Vector icon rendering resolution fallback (if we can't infer via AppWidgetManager for some reason)
private const val DEFAULT_MAX_ICON_SIZE = 512
@ -141,25 +145,36 @@ class ButtonWidget : AppWidgetProvider() {
super.onReceive(context, intent)
when (action) {
CALL_SERVICE_AUTH -> authThenCallConfiguredService(context, appWidgetId)
CALL_SERVICE -> callConfiguredService(context, appWidgetId)
RECEIVE_DATA -> saveServiceCallConfiguration(context, intent.extras, appWidgetId)
Intent.ACTION_SCREEN_ON -> updateAllWidgets(context)
}
}
private fun authThenCallConfiguredService(context: Context, appWidgetId: Int) {
Log.d(TAG, "Calling authentication, then configured service")
val intent = Intent(context, WidgetAuthenticationActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
context.startActivity(intent)
}
private fun getWidgetRemoteViews(context: Context, appWidgetId: Int): RemoteViews {
// Every time AppWidgetManager.updateAppWidget(...) is called, the button listener
// and label need to be re-assigned, or the next time the layout updates
// (e.g home screen rotation) the widget will fall back on its default layout
// without any click listener being applied
val widget = buttonWidgetDao.get(appWidgetId)
val auth = widget?.requireAuthentication == true
val intent = Intent(context, ButtonWidget::class.java).apply {
action = CALL_SERVICE
action = if (auth) CALL_SERVICE_AUTH else CALL_SERVICE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val widget = buttonWidgetDao.get(appWidgetId)
// Create an icon pack and load all drawables.
if (iconPack == null) {
val loader = IconPackLoader(context)
@ -339,6 +354,7 @@ class ButtonWidget : AppWidgetProvider() {
val service: String? = extras.getString(EXTRA_SERVICE)
val serviceData: String? = extras.getString(EXTRA_SERVICE_DATA)
val label: String? = extras.getString(EXTRA_LABEL)
val requireAuthentication: Boolean = extras.getBoolean(EXTRA_REQUIRE_AUTHENTICATION)
val icon: Int = extras.getInt(EXTRA_ICON)
val backgroundType: WidgetBackgroundType = extras.getSerializable(EXTRA_BACKGROUND_TYPE) as WidgetBackgroundType
val textColor: String? = extras.getString(EXTRA_TEXT_COLOR)
@ -355,10 +371,11 @@ class ButtonWidget : AppWidgetProvider() {
"domain: " + domain + System.lineSeparator() +
"service: " + service + System.lineSeparator() +
"service_data: " + serviceData + System.lineSeparator() +
"require_authentication: " + requireAuthentication + System.lineSeparator() +
"label: " + label
)
val widget = ButtonWidgetEntity(appWidgetId, icon, domain, service, serviceData, label, backgroundType, textColor)
val widget = ButtonWidgetEntity(appWidgetId, icon, domain, service, serviceData, label, backgroundType, textColor, requireAuthentication)
buttonWidgetDao.add(widget)
// It is the responsibility of the configuration activity to update the app widget

View file

@ -223,6 +223,8 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity(), IconDialog.
buttonWidget.textColor?.let { it.toColorInt() == ContextCompat.getColor(this, commonR.color.colorWidgetButtonLabelBlack) } ?: false
binding.addButton.setText(commonR.string.update_widget)
binding.widgetCheckboxRequireAuthentication.isChecked = buttonWidget.requireAuthentication
} else {
binding.backgroundType.setSelection(0)
}
@ -463,6 +465,8 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity(), IconDialog.
else null
)
intent.putExtra(ButtonWidget.EXTRA_REQUIRE_AUTHENTICATION, binding.widgetCheckboxRequireAuthentication.isChecked)
context.sendBroadcast(intent)
// Make sure we pass back the original appWidgetId

View file

@ -0,0 +1,51 @@
package io.homeassistant.companion.android.widgets.common
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.homeassistant.companion.android.authenticator.Authenticator
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.widgets.button.ButtonWidget
class WidgetAuthenticationActivity : AppCompatActivity() {
companion object {
private const val TAG = "WidgetAuthenticationA"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate in WidgetAuthenticationActivity called")
val authenticator = Authenticator(this, this, ::authenticationResult)
authenticator.authenticate(getString(R.string.biometric_set_title))
}
private fun authenticationResult(result: Int) {
when (result) {
Authenticator.SUCCESS -> {
Log.d(TAG, "Authentication successful, calling requested service")
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId > -1) {
val intent = Intent(applicationContext, ButtonWidget::class.java).apply {
action = ButtonWidget.CALL_SERVICE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
sendBroadcast(intent)
}
finishAffinity()
}
Authenticator.CANCELED -> {
Log.d(TAG, "Authentication canceled by user, closing activity")
finishAffinity()
}
else -> {
Log.d(TAG, "Authentication failed, retry attempts allowed")
Toast.makeText(applicationContext, getString(R.string.widget_error_authenticating), Toast.LENGTH_LONG).show()
finishAffinity()
}
}
}
}

View file

@ -163,6 +163,12 @@
</RadioGroup>
</LinearLayout>
<CheckBox
android:id="@+id/widget_checkbox_require_authentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/widget_checkbox_require_authentication" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/add_button"
android:layout_width="wrap_content"

View file

@ -106,4 +106,13 @@
<style name="Alert.Button.Negative" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Theme.Transparent" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>

View file

@ -0,0 +1,720 @@
{
"formatVersion": 1,
"database": {
"version": 32,
"identityHash": "33c486a8044395ef40a023dc4a7ab95d",
"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": {
"columnNames": [
"sensor_id",
"name"
],
"autoGenerate": false
},
"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": {
"columnNames": [
"host"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sensors",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `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`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"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": {
"columnNames": [
"sensor_id",
"name"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "button_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `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": "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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "camera_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entityId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "mediaplayctrls_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, `label` TEXT, `showSkip` INTEGER NOT NULL, `showSeek` INTEGER NOT NULL, `showVolume` INTEGER NOT NULL, `showSource` 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": "entityId",
"columnName": "entityId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "showSkip",
"columnName": "showSkip",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSeek",
"columnName": "showSeek",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showVolume",
"columnName": "showVolume",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSource",
"columnName": "showSource",
"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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "static_widget",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `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": "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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "template_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `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": "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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"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)",
"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
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "qs_tiles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tileId` TEXT NOT NULL, `icon_id` INTEGER, `entityId` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tileId",
"columnName": "tileId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "iconId",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "entityId",
"columnName": "entityId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subtitle",
"columnName": "subtitle",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"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": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "entityStateComplications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entityId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocketSetting` TEXT NOT NULL, `sensorUpdateFrequency` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "websocketSetting",
"columnName": "websocketSetting",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sensorUpdateFrequency",
"columnName": "sensorUpdateFrequency",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"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, '33c486a8044395ef40a023dc4a7ab95d')"
]
}
}

View file

@ -74,7 +74,7 @@ import io.homeassistant.companion.android.common.R as commonR
EntityStateComplications::class,
Setting::class
],
version = 31,
version = 32,
autoMigrations = [
AutoMigration(from = 24, to = 25),
AutoMigration(from = 25, to = 26),
@ -83,6 +83,7 @@ import io.homeassistant.companion.android.common.R as commonR
AutoMigration(from = 28, to = 29),
AutoMigration(from = 29, to = 30),
AutoMigration(from = 30, to = 31),
AutoMigration(from = 31, to = 32),
]
)
@TypeConverters(

View file

@ -21,5 +21,7 @@ data class ButtonWidgetEntity(
@ColumnInfo(name = "background_type", defaultValue = "DAYNIGHT")
override val backgroundType: WidgetBackgroundType = WidgetBackgroundType.DAYNIGHT,
@ColumnInfo(name = "text_color")
override val textColor: String? = null
override val textColor: String? = null,
@ColumnInfo(name = "require_authentication", defaultValue = "0")
val requireAuthentication: Boolean
) : WidgetEntity, ThemeableWidgetEntity

View file

@ -899,4 +899,6 @@
<string name="tls_cert_expired_message">The certificate is not yet or no longer valid.\nPlease install a new credential on your phone and try again.</string>
<string name="basic_sensor_name_battery_power">Battery Power</string>
<string name="sensor_description_battery_power">The current wattage of the device. To get the most out of this sensor consider using the \"Fast while charging\" sensor update frequency setting.</string>
<string name="widget_checkbox_require_authentication">Require Authentication</string>
<string name="widget_error_authenticating">Error authenticating - is an authentication method configured?</string>
</resources>