Send device-preferred Thread dataset if different from core (#3426)

- Expand the automatic Thread dataset sync on Matter commissioning to also send the device-preferred Thread dataset if it's different from core (it will either receive something new or update)
This commit is contained in:
Joris Pelgröm 2023-04-01 04:57:21 +02:00 committed by GitHub
parent 45c4e16074
commit 300edaa26b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 15 deletions

View file

@ -2,6 +2,7 @@ package io.homeassistant.companion.android.matter
import android.app.Application
import android.content.IntentSender
import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -22,6 +23,10 @@ class MatterCommissioningViewModel @Inject constructor(
application: Application
) : AndroidViewModel(application) {
companion object {
private const val TAG = "MatterCommissioningView"
}
sealed class CommissioningFlowStep {
object NotStarted : CommissioningFlowStep()
object NotRegistered : CommissioningFlowStep()
@ -84,11 +89,16 @@ class MatterCommissioningViewModel @Inject constructor(
suspend fun syncThreadIfNecessary(): IntentSender? {
step = CommissioningFlowStep.Working
return threadManager.syncPreferredDataset(
getApplication<Application>().applicationContext,
serverId,
viewModelScope
)
return try {
threadManager.syncPreferredDataset(
getApplication<Application>().applicationContext,
serverId,
viewModelScope
)
} catch (e: Exception) {
Log.w(TAG, "Unable to sync preferred Thread dataset, continuing", e)
null
}
}
fun onThreadPermissionResult(result: ActivityResult, code: String) {

View file

@ -6,6 +6,7 @@ import android.content.IntentSender
import android.os.Build
import android.util.Log
import androidx.activity.result.ActivityResult
import com.google.android.gms.threadnetwork.IsPreferredCredentialsResult
import com.google.android.gms.threadnetwork.ThreadBorderAgent
import com.google.android.gms.threadnetwork.ThreadNetwork
import com.google.android.gms.threadnetwork.ThreadNetworkCredentials
@ -40,6 +41,9 @@ class ThreadManagerImpl @Inject constructor(
HomeAssistantVersion.fromString(config.version)?.isAtLeast(2023, 3, 0) == true
}
private suspend fun getDatasetsFromServer(serverId: Int): List<ThreadDatasetResponse>? =
serverManager.webSocketRepository(serverId).getThreadDatasets()
override suspend fun syncPreferredDataset(
context: Context,
serverId: Int,
@ -48,9 +52,10 @@ class ThreadManagerImpl @Inject constructor(
if (!appSupportsThread() || !coreSupportsThread(serverId)) return null
val getDeviceDataset = scope.async { getPreferredDatasetFromDevice(context) }
val getCoreDataset = scope.async { getPreferredDatasetFromServer(serverId) }
val getCoreDatasets = scope.async { getDatasetsFromServer(serverId) }
val deviceThreadIntent = getDeviceDataset.await()
val coreThreadDataset = getCoreDataset.await()
val coreThreadDatasets = getCoreDatasets.await()
val coreThreadDataset = coreThreadDatasets?.firstOrNull { it.preferred }
if (deviceThreadIntent == null && coreThreadDataset != null) {
try {
@ -62,15 +67,23 @@ class ThreadManagerImpl @Inject constructor(
} else if (deviceThreadIntent != null && coreThreadDataset == null) {
Log.d(TAG, "Thread export is ready")
return deviceThreadIntent
} // else if device and core both have or don't have datasets, continue
} else if (deviceThreadIntent != null && coreThreadDataset != null) {
try {
val coreIsDevicePreferred = isPreferredDatasetByDevice(context, coreThreadDataset.datasetId, serverId)
Log.d(TAG, "Thread: device ${if (coreIsDevicePreferred) "prefers" else "doesn't prefer" } core preferred dataset")
if (!coreIsDevicePreferred) {
return deviceThreadIntent // Import the dataset to core
}
} catch (e: Exception) {
Log.e(TAG, "Thread device/core preferred comparison failed", e)
}
} // else if device and core both don't have datasets, continue
return null
}
override suspend fun getPreferredDatasetFromServer(serverId: Int): ThreadDatasetResponse? {
val datasets = serverManager.webSocketRepository(serverId).getThreadDatasets()
return datasets?.firstOrNull { it.preferred }
}
override suspend fun getPreferredDatasetFromServer(serverId: Int): ThreadDatasetResponse? =
getDatasetsFromServer(serverId)?.firstOrNull { it.preferred }
override suspend fun importDatasetFromServer(context: Context, datasetId: String, serverId: Int) {
val tlv = serverManager.webSocketRepository(serverId).getThreadDatasetTlv(datasetId)?.tlvAsByteArray
@ -96,6 +109,20 @@ 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) {
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) }
}
}
return false
}
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int) {
if (result.resultCode == Activity.RESULT_OK && coreSupportsThread(serverId)) {
val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)

View file

@ -20,8 +20,8 @@ interface ThreadManager {
/**
* Try to sync the preferred Thread dataset with the device and server. If one has a preferred
* dataset while the other one doesn't, it will sync. If both have or don't have preferred
* datasets, skip syncing.
* dataset while the other one doesn't, it will sync. If both have preferred datasets, it will
* send updated data to the server if needed. If neither has a preferred dataset, skip syncing.
* @return [IntentSender] if permission is required to import the device dataset, `null` if
* syncing completed or there is nothing to sync
*/

View file

@ -332,7 +332,12 @@ class WebViewPresenterImpl @Inject constructor(
_matterCommissioningStatus.tryEmit(MatterFrontendCommissioningStatus.REQUESTED)
mainScope.launch {
val deviceThreadIntent = threadUseCase.syncPreferredDataset(context, serverId, this)
val deviceThreadIntent = try {
threadUseCase.syncPreferredDataset(context, serverId, this)
} catch (e: Exception) {
Log.w(TAG, "Unable to sync preferred Thread dataset, continuing", e)
null
}
if (deviceThreadIntent != null) {
matterCommissioningIntentSender = deviceThreadIntent
_matterCommissioningStatus.tryEmit(MatterFrontendCommissioningStatus.THREAD_EXPORT_TO_SERVER)