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..e0ff31df87b 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
@@ -78,7 +78,7 @@ class ThreadManagerImpl @Inject constructor(
return if (getDeviceDataset == null) {
ThreadManager.SyncResult.NoneHaveCredentials
} else {
- val appIsDevicePreferred = appAddedIsPreferredCredentials(context)
+ val appIsDevicePreferred = appAddedPreferredCredential(context) != null
Timber.d("Thread: device ${if (appIsDevicePreferred) "prefers" else "doesn't prefer" } dataset from app")
return if (appIsDevicePreferred) {
@@ -128,13 +128,16 @@ class ThreadManagerImpl @Inject constructor(
Timber.d(
"Thread: device ${if (coreIsDevicePreferred) "prefers" else "doesn't prefer" } core preferred dataset",
)
- val appIsDevicePreferred = coreIsDevicePreferred || appAddedIsPreferredCredentials(context)
+ val appPreferredCredential = if (coreIsDevicePreferred) null else appAddedPreferredCredential(context)
+ val appIsDevicePreferred = coreIsDevicePreferred || appPreferredCredential != null
Timber.d(
"Thread: device ${if (appIsDevicePreferred) "prefers" else "doesn't prefer" } dataset from app",
)
var exportFromDevice = false
var updated: Boolean? = null
+ var deviceNowPrefersCore: Boolean? = null
+ var devicePreferredNetworkName: String? = null
if (!coreIsDevicePreferred) {
if (appIsDevicePreferred) {
// Update or remove the device preferred credential to match core state.
@@ -169,6 +172,27 @@ class ThreadManagerImpl @Inject constructor(
)
}
Timber.d("Thread update device completed: deleted ${localIds.size} datasets, updated 1")
+ // ThreadNetworkClient.addCredentials does not promote the new
+ // credential to preferred. Verify what Play Services chose so the
+ // caller can report honestly instead of assuming success.
+ deviceNowPrefersCore = try {
+ isPreferredDatasetByDevice(context, coreThreadDataset.datasetId, serverId)
+ } catch (e: Exception) {
+ Timber.w(e, "Unable to verify preferred dataset after import")
+ null
+ }
+ Timber.d(
+ "Thread: after import device ${
+ when (deviceNowPrefersCore) {
+ true -> "now prefers"
+ false -> "still doesn't prefer"
+ null -> "preference state unknown for"
+ }
+ } core preferred dataset",
+ )
+ if (deviceNowPrefersCore == false) {
+ devicePreferredNetworkName = appAddedPreferredCredential(context)?.networkName
+ }
true
} else { // Core prefers imported from other app, this shouldn't be managed by HA
localIds.forEach { baId ->
@@ -197,6 +221,8 @@ class ThreadManagerImpl @Inject constructor(
matches = coreIsDevicePreferred,
fromApp = appIsDevicePreferred,
updated = updated,
+ deviceNowPrefersCore = deviceNowPrefersCore,
+ devicePreferredNetworkName = devicePreferredNetworkName,
exportIntent = if (exportFromDevice) deviceThreadIntent else null,
)
} catch (e: Exception) {
@@ -205,6 +231,8 @@ class ThreadManagerImpl @Inject constructor(
matches = null,
fromApp = null,
updated = null,
+ deviceNowPrefersCore = null,
+ devicePreferredNetworkName = null,
exportIntent = null,
)
}
@@ -262,7 +290,8 @@ class ThreadManagerImpl @Inject constructor(
}
}
- private suspend fun appAddedIsPreferredCredentials(context: Context): Boolean {
+ @OptIn(ExperimentalStdlibApi::class)
+ private suspend fun appAddedPreferredCredential(context: Context): ThreadNetworkCredentials? {
val appCredentials = suspendCoroutine { cont ->
ThreadNetwork.getNetworkClient(context)
.allCredentials
@@ -270,20 +299,17 @@ class ThreadManagerImpl @Inject constructor(
.addOnFailureListener { cont.resume(null) }
}
return try {
- appCredentials?.any {
- val isPreferred = isPreferredCredentials(context, it)
- if (isPreferred) {
- Timber.d(
- "Thread device prefers app added dataset: ${it.networkName} (PAN ${it.panId}, EXTPAN ${String(
- it.extendedPanId,
- )})",
- )
- }
- isPreferred
- } ?: false
+ appCredentials?.firstOrNull { isPreferredCredentials(context, it) }?.also {
+ Timber.d(
+ "Thread device prefers app added dataset: %s (PAN %s, EXTPAN %s)",
+ it.networkName,
+ it.panId,
+ it.extendedPanId.toHexString(HexFormat.UpperCase),
+ )
+ }
} catch (e: Exception) {
Timber.e(e, "Thread app added credentials preferred check failed")
- false
+ null
}
}
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..63703acd15b 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
@@ -100,10 +100,21 @@ class DeveloperSettingsPresenterImpl @Inject constructor(
} else if (syncResult.matches == true) {
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_match), true)
} else if (syncResult.fromApp == true && syncResult.updated == true) {
- view.onThreadDebugResult(
- context.getString(commonR.string.thread_debug_result_updated),
- true,
- )
+ val message = if (syncResult.deviceNowPrefersCore == false) {
+ val base = context.getString(commonR.string.thread_debug_result_updated_not_preferred)
+ val name = syncResult.devicePreferredNetworkName
+ if (name != null) {
+ "$base ${context.getString(
+ commonR.string.thread_debug_result_mismatch_detail,
+ name,
+ )}"
+ } else {
+ base
+ }
+ } else {
+ context.getString(commonR.string.thread_debug_result_updated)
+ }
+ view.onThreadDebugResult(message, syncResult.deviceNowPrefersCore != false)
} else if (syncResult.fromApp == true && syncResult.updated == false) {
view.onThreadDebugResult(
context.getString(commonR.string.thread_debug_result_removed),
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..8bd7ad762d2 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
@@ -18,6 +18,8 @@ interface ThreadManager {
val matches: Boolean?,
val fromApp: Boolean?,
val updated: Boolean?,
+ val deviceNowPrefersCore: Boolean?,
+ val devicePreferredNetworkName: String?,
val exportIntent: IntentSender?,
) : SyncResult()
object NoneHaveCredentials : SyncResult()
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 8f2e214a324..0d38e7ecfbb 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -1178,6 +1178,7 @@
Removed old network from Home Assistant on this device
The Home Assistant server does not support Thread
Updated network from Home Assistant on this device
+ Updated network from Home Assistant on this device, but the device still prefers a different network
Manually update device and server Thread credentials and verify results
Imported credential
You don\'t have any credentials to import.