Skip to content

Commit

Permalink
Update Thread network sync to update/delete HA networks (#3676)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpelgrom authored Jul 18, 2023
1 parent ef916f6 commit 7b79024
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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? {
Expand All @@ -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) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
2 changes: 2 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 7b79024

Please sign in to comment.