diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt b/app/src/full/kotlin/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt index 6c2c5e70ce0..929fd12f5de 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/thread/ThreadManagerImpl.kt @@ -37,13 +37,16 @@ class ThreadManagerImpl @Inject constructor( !packageManager.isAutomotive() override suspend fun coreSupportsThread(serverId: Int): Boolean { - if (!serverManager.isRegistered() || serverManager.getServer(serverId)?.user?.isAdmin != true) return false + if (!serverManager.isRegistered() || serverManager.getServer(serverId) == null) return false val config = serverManager.webSocketRepository(serverId).getConfig() return config != null && config.components.contains("thread") && HomeAssistantVersion.fromString(config.version)?.isAtLeast(2023, 3, 0) == true } + private fun userIsAdmin(serverId: Int): Boolean = + serverManager.getServer(serverId)?.user?.isAdmin == true + private suspend fun getDatasetsFromServer(serverId: Int): List? = serverManager.webSocketRepository(serverId).getThreadDatasets() @@ -55,6 +58,7 @@ class ThreadManagerImpl @Inject constructor( ): ThreadManager.SyncResult { if (!appSupportsThread()) return ThreadManager.SyncResult.AppUnsupported if (!coreSupportsThread(serverId)) return ThreadManager.SyncResult.ServerUnsupported + if (!userIsAdmin(serverId)) return ThreadManager.SyncResult.ServerUserNotAdmin return if (exportOnly) { // Limited sync, only export non-app dataset exportSyncPreferredDataset(context) @@ -296,7 +300,7 @@ class ThreadManagerImpl @Inject constructor( } override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int): String? { - if (result.resultCode == Activity.RESULT_OK && coreSupportsThread(serverId)) { + if (result.resultCode == Activity.RESULT_OK && coreSupportsThread(serverId) && userIsAdmin(serverId)) { val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!) try { val added = serverManager.webSocketRepository( diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/settings/developer/DeveloperSettingsPresenterImpl.kt b/app/src/main/kotlin/io/homeassistant/companion/android/settings/developer/DeveloperSettingsPresenterImpl.kt index 47edcd6ddd9..301d4ccfb17 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/settings/developer/DeveloperSettingsPresenterImpl.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/settings/developer/DeveloperSettingsPresenterImpl.kt @@ -79,6 +79,11 @@ class DeveloperSettingsPresenterImpl @Inject constructor( context.getString(commonR.string.thread_debug_result_unsupported_server), false, ) + is ThreadManager.SyncResult.ServerUserNotAdmin -> + view.onThreadDebugResult( + context.getString(commonR.string.thread_debug_result_user_not_admin), + false, + ) is ThreadManager.SyncResult.OnlyOnServer -> { if (syncResult.imported) { view.onThreadDebugResult( diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/thread/ThreadManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/thread/ThreadManager.kt index 3af998d53ff..8d8551ddc6d 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/thread/ThreadManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/thread/ThreadManager.kt @@ -11,6 +11,7 @@ interface ThreadManager { sealed class SyncResult { object AppUnsupported : SyncResult() object ServerUnsupported : SyncResult() + object ServerUserNotAdmin : SyncResult() object NotConnected : SyncResult() class OnlyOnServer(val imported: Boolean) : SyncResult() class OnlyOnDevice(val exportIntent: IntentSender?) : SyncResult() @@ -29,7 +30,9 @@ interface ThreadManager { fun appSupportsThread(): Boolean /** - * Indicates if the server supports Thread credential management. + * Indicates if the server has the Thread component installed and is on a recent enough + * Home Assistant version for credential management. This does not consider whether the + * signed-in user is allowed to manage Thread credentials (which requires admin privileges). */ suspend fun coreSupportsThread(serverId: Int): Boolean diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/webview/MatterThreadStep.kt b/app/src/main/kotlin/io/homeassistant/companion/android/webview/MatterThreadStep.kt index 212bf4d73b9..02c5a4605e0 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/webview/MatterThreadStep.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/webview/MatterThreadStep.kt @@ -11,5 +11,6 @@ enum class MatterThreadStep { ERROR_MATTER_CANCELLED, ERROR_MATTER_OTHER, ERROR_THREAD_LOCAL_NETWORK, + ERROR_THREAD_USER_NOT_ADMIN, ERROR_THREAD_OTHER, } diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt index 6a1ede4c700..e9c02f3bf05 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt @@ -857,6 +857,15 @@ class WebViewActivity : presenter.finishMatterThreadFlow() } + MatterThreadStep.ERROR_THREAD_USER_NOT_ADMIN -> { + alertDialog?.cancel() + AlertDialog.Builder(this@WebViewActivity) + .setMessage(commonR.string.thread_export_user_not_admin) + .setPositiveButton(commonR.string.ok, null) + .show() + presenter.finishMatterThreadFlow() + } + else -> {} // Do nothing } } diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImpl.kt b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImpl.kt index f6c05491366..72942980b4e 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImpl.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImpl.kt @@ -570,6 +570,10 @@ class WebViewPresenterImpl @Inject constructor( mutableMatterThreadStep.tryEmit(MatterThreadStep.ERROR_THREAD_LOCAL_NETWORK) } + is ThreadManager.SyncResult.ServerUserNotAdmin -> { + mutableMatterThreadStep.tryEmit(MatterThreadStep.ERROR_THREAD_USER_NOT_ADMIN) + } + else -> { mutableMatterThreadStep.tryEmit(MatterThreadStep.ERROR_THREAD_OTHER) } diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8f2e214a324..68867837396 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1177,11 +1177,13 @@ No credentials to sync Removed old network from Home Assistant on this device The Home Assistant server does not support Thread + Managing Thread credentials requires an administrator account on the Home Assistant server Updated network from Home Assistant on this device Manually update device and server Thread credentials and verify results Imported credential You don\'t have any credentials to import. You are not connected to a local network. Connect to Wi-Fi or ethernet to import Thread credentials. + Managing Thread credentials requires an administrator account on the Home Assistant server. Thread is currently unavailable Vibrate when selected Requires unlocked device