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.