Update Thread network sync to update/delete HA networks (#3676)

This commit is contained in:
Joris Pelgröm 2023-07-19 00:43:39 +02:00 committed by GitHub
parent ef916f606b
commit 7b79024c1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 11 deletions

View file

@ -76,14 +76,41 @@ class ThreadManagerImpl @Inject constructor(
try {
val coreIsDevicePreferred = isPreferredDatasetByDevice(context, coreThreadDataset.datasetId, serverId)
Log.d(TAG, "Thread: device ${if (coreIsDevicePreferred) "prefers" else "doesn't prefer" } core preferred dataset")
val appIsDevicePreferred = coreIsDevicePreferred || appAddedIsPreferredCredentials(context)
Log.d(TAG, "Thread: device ${if (appIsDevicePreferred) "prefers" else "doesn't prefer" } dataset from app")
var exportFromDevice = false
var updated: Boolean? = null
if (!coreIsDevicePreferred) {
if (appIsDevicePreferred) {
// Update or remove the device preferred credential to match core state
try {
updated = if (coreThreadDataset.source != "Google") { // Credential from HA, update
importDatasetFromServer(context, coreThreadDataset.datasetId, serverId)
true
} else { // Imported from another app, so this shouldn't be managed by HA
deleteThreadCredential(context)
false
}
Log.d(TAG, "Thread update device completed")
} catch (e: Exception) {
Log.e(TAG, "Thread update device failed", e)
}
} else {
exportFromDevice = true
}
}
// Import the dataset to core if different from device
ThreadManager.SyncResult.AllHaveCredentials(
matches = coreIsDevicePreferred,
exportIntent = if (coreIsDevicePreferred) null else deviceThreadIntent
fromApp = appIsDevicePreferred,
updated = updated,
exportIntent = if (exportFromDevice) deviceThreadIntent else null
)
} catch (e: Exception) {
Log.e(TAG, "Thread device/core preferred comparison failed", e)
ThreadManager.SyncResult.AllHaveCredentials(matches = null, exportIntent = null)
ThreadManager.SyncResult.AllHaveCredentials(matches = null, fromApp = null, updated = null, exportIntent = null)
}
} else {
ThreadManager.SyncResult.NoneHaveCredentials
@ -119,16 +146,34 @@ class ThreadManagerImpl @Inject constructor(
private suspend fun isPreferredDatasetByDevice(context: Context, datasetId: String, serverId: Int): Boolean {
val tlv = serverManager.webSocketRepository(serverId).getThreadDatasetTlv(datasetId)?.tlvAsByteArray
if (tlv != null) {
return if (tlv != null) {
val threadNetworkCredentials = ThreadNetworkCredentials.fromActiveOperationalDataset(tlv)
return suspendCoroutine { cont ->
ThreadNetwork.getClient(context)
.isPreferredCredentials(threadNetworkCredentials)
.addOnSuccessListener { cont.resume(it == IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED) }
.addOnFailureListener { cont.resumeWithException(it) }
}
isPreferredCredentials(context, threadNetworkCredentials)
} else {
false
}
return false
}
private suspend fun appAddedIsPreferredCredentials(context: Context): Boolean {
val appCredentials = suspendCoroutine { cont ->
ThreadNetwork.getClient(context)
.allCredentials
.addOnSuccessListener { cont.resume(it) }
.addOnFailureListener { cont.resume(null) }
}
return try {
appCredentials?.any { isPreferredCredentials(context, it) } ?: false
} catch (e: Exception) {
Log.e(TAG, "Thread app added credentials preferred check failed", e)
false
}
}
private suspend fun isPreferredCredentials(context: Context, credentials: ThreadNetworkCredentials): Boolean = suspendCoroutine { cont ->
ThreadNetwork.getClient(context)
.isPreferredCredentials(credentials)
.addOnSuccessListener { cont.resume(it == IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED) }
.addOnFailureListener { cont.resumeWithException(it) }
}
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int): String? {
@ -143,4 +188,13 @@ class ThreadManagerImpl @Inject constructor(
}
return null
}
private suspend fun deleteThreadCredential(context: Context) = suspendCoroutine { cont ->
// This only works because we currently always use the same border agent ID
val threadBorderAgent = ThreadBorderAgent.newBuilder(BORDER_AGENT_ID.toByteArray()).build()
ThreadNetwork.getClient(context)
.removeCredentials(threadBorderAgent)
.addOnSuccessListener { cont.resume(true) }
.addOnFailureListener { cont.resumeWithException(it) }
}
}

View file

@ -83,6 +83,10 @@ class DeveloperSettingsPresenterImpl @Inject constructor(
view.onThreadPermissionRequest(syncResult.exportIntent, serverId, false)
} 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)
} else if (syncResult.fromApp == true && syncResult.updated == false) {
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_removed), true)
} else {
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
}

View file

@ -13,7 +13,7 @@ interface ThreadManager {
object ServerUnsupported : SyncResult()
class OnlyOnServer(val imported: Boolean) : SyncResult()
class OnlyOnDevice(val exportIntent: IntentSender?) : SyncResult()
class AllHaveCredentials(val matches: Boolean?, val exportIntent: IntentSender?) : SyncResult()
class AllHaveCredentials(val matches: Boolean?, val fromApp: Boolean?, val updated: Boolean?, val exportIntent: IntentSender?) : SyncResult()
object NoneHaveCredentials : SyncResult()
}

View file

@ -1084,7 +1084,9 @@
<string name="thread_debug_result_mismatch">Home Assistant and this device prefer different networks</string>
<string name="thread_debug_result_mismatch_detail">(device prefers: %1$s)</string>
<string name="thread_debug_result_none">No credentials to sync</string>
<string name="thread_debug_result_removed">Removed old network from Home Assistant on this device</string>
<string name="thread_debug_result_unsupported_server">The Home Assistant server does not support Thread</string>
<string name="thread_debug_result_updated">Updated network from Home Assistant on this device</string>
<string name="thread_debug_summary">Manually update device and server Thread credentials and verify results</string>
<string name="tile_vibrate">Vibrate when clicked</string>
<string name="tile_auth_required">Requires unlocked device</string>