mirror of
https://github.com/home-assistant/android
synced 2024-10-15 12:32:54 +00:00
Add troubleshooting menu to settings, with Thread credentials sync (#3614)
* Add troubleshooting menu to settings, with Thread credentials sync - Add a troubleshooting menu to the app settings which includes logs/debugging settings - Add an option to manually sync Thread credentials and view the result * Less technical messages matching frontend
This commit is contained in:
parent
c75b315d81
commit
1ed0f6a094
|
@ -94,11 +94,16 @@ class MatterCommissioningViewModel @Inject constructor(
|
|||
suspend fun syncThreadIfNecessary(): IntentSender? {
|
||||
step = CommissioningFlowStep.Working
|
||||
return try {
|
||||
threadManager.syncPreferredDataset(
|
||||
val result = threadManager.syncPreferredDataset(
|
||||
getApplication<Application>().applicationContext,
|
||||
serverId,
|
||||
viewModelScope
|
||||
)
|
||||
when (result) {
|
||||
is ThreadManager.SyncResult.OnlyOnDevice -> result.exportIntent
|
||||
is ThreadManager.SyncResult.AllHaveCredentials -> result.exportIntent
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to sync preferred Thread dataset, continuing", e)
|
||||
null
|
||||
|
|
|
@ -48,8 +48,9 @@ class ThreadManagerImpl @Inject constructor(
|
|||
context: Context,
|
||||
serverId: Int,
|
||||
scope: CoroutineScope
|
||||
): IntentSender? {
|
||||
if (!appSupportsThread() || !coreSupportsThread(serverId)) return null
|
||||
): ThreadManager.SyncResult {
|
||||
if (!appSupportsThread()) return ThreadManager.SyncResult.AppUnsupported
|
||||
if (!coreSupportsThread(serverId)) return ThreadManager.SyncResult.ServerUnsupported
|
||||
|
||||
val getDeviceDataset = scope.async { getPreferredDatasetFromDevice(context) }
|
||||
val getCoreDatasets = scope.async { getDatasetsFromServer(serverId) }
|
||||
|
@ -57,29 +58,34 @@ class ThreadManagerImpl @Inject constructor(
|
|||
val coreThreadDatasets = getCoreDatasets.await()
|
||||
val coreThreadDataset = coreThreadDatasets?.firstOrNull { it.preferred }
|
||||
|
||||
if (deviceThreadIntent == null && coreThreadDataset != null) {
|
||||
return if (deviceThreadIntent == null && coreThreadDataset != null) {
|
||||
try {
|
||||
importDatasetFromServer(context, coreThreadDataset.datasetId, serverId)
|
||||
Log.d(TAG, "Thread import to device completed")
|
||||
ThreadManager.SyncResult.OnlyOnServer(imported = true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Thread import to device failed", e)
|
||||
ThreadManager.SyncResult.OnlyOnServer(imported = false)
|
||||
}
|
||||
} else if (deviceThreadIntent != null && coreThreadDataset == null) {
|
||||
Log.d(TAG, "Thread export is ready")
|
||||
return deviceThreadIntent
|
||||
ThreadManager.SyncResult.OnlyOnDevice(exportIntent = deviceThreadIntent)
|
||||
} 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
|
||||
}
|
||||
// Import the dataset to core if different from device
|
||||
ThreadManager.SyncResult.AllHaveCredentials(
|
||||
matches = coreIsDevicePreferred,
|
||||
exportIntent = if (coreIsDevicePreferred) null else deviceThreadIntent
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Thread device/core preferred comparison failed", e)
|
||||
ThreadManager.SyncResult.AllHaveCredentials(matches = null, exportIntent = null)
|
||||
}
|
||||
} else {
|
||||
ThreadManager.SyncResult.NoneHaveCredentials
|
||||
}
|
||||
} // else if device and core both don't have datasets, continue
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getPreferredDatasetFromServer(serverId: Int): ThreadDatasetResponse? =
|
||||
|
@ -123,14 +129,16 @@ class ThreadManagerImpl @Inject constructor(
|
|||
return false
|
||||
}
|
||||
|
||||
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int) {
|
||||
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int): String? {
|
||||
if (result.resultCode == Activity.RESULT_OK && coreSupportsThread(serverId)) {
|
||||
val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
|
||||
try {
|
||||
serverManager.webSocketRepository(serverId).addThreadDataset(threadNetworkCredentials.activeOperationalDataset)
|
||||
val added = serverManager.webSocketRepository(serverId).addThreadDataset(threadNetworkCredentials.activeOperationalDataset)
|
||||
if (added) return threadNetworkCredentials.networkName
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while executing server new Thread credentials request", e)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ import io.homeassistant.companion.android.database.server.Server
|
|||
import io.homeassistant.companion.android.nfc.NfcSetupActivity
|
||||
import io.homeassistant.companion.android.onboarding.OnboardApp
|
||||
import io.homeassistant.companion.android.settings.controls.ManageControlsSettingsFragment
|
||||
import io.homeassistant.companion.android.settings.developer.DeveloperSettingsFragment
|
||||
import io.homeassistant.companion.android.settings.language.LanguagesProvider
|
||||
import io.homeassistant.companion.android.settings.log.LogFragment
|
||||
import io.homeassistant.companion.android.settings.notification.NotificationChannelFragment
|
||||
import io.homeassistant.companion.android.settings.notification.NotificationHistoryFragment
|
||||
import io.homeassistant.companion.android.settings.qs.ManageTilesFragment
|
||||
|
@ -292,10 +292,10 @@ class SettingsFragment(
|
|||
it.intent = Intent(Intent.ACTION_VIEW, Uri.parse(it.summary.toString()))
|
||||
}
|
||||
|
||||
findPreference<Preference>("show_share_logs")?.setOnPreferenceClickListener {
|
||||
findPreference<Preference>("developer")?.setOnPreferenceClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.content, LogFragment::class.java, null)
|
||||
addToBackStack(getString(commonR.string.log))
|
||||
replace(R.id.content, DeveloperSettingsFragment::class.java, null)
|
||||
addToBackStack(getString(commonR.string.troubleshooting))
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import io.homeassistant.companion.android.settings.developer.DeveloperSettingsPresenter
|
||||
import io.homeassistant.companion.android.settings.developer.DeveloperSettingsPresenterImpl
|
||||
import io.homeassistant.companion.android.settings.server.ServerSettingsPresenter
|
||||
import io.homeassistant.companion.android.settings.server.ServerSettingsPresenterImpl
|
||||
|
||||
|
@ -11,6 +13,9 @@ import io.homeassistant.companion.android.settings.server.ServerSettingsPresente
|
|||
@InstallIn(ActivityComponent::class)
|
||||
abstract class SettingsModule {
|
||||
|
||||
@Binds
|
||||
abstract fun developerSettingsPresenter(developerSettingsPresenterImpl: DeveloperSettingsPresenterImpl): DeveloperSettingsPresenter
|
||||
|
||||
@Binds
|
||||
abstract fun serverSettingsPresenter(serverSettingsPresenterImpl: ServerSettingsPresenterImpl): ServerSettingsPresenter
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ class SettingsPresenterImpl @Inject constructor(
|
|||
"crash_reporting" -> prefsRepository.isCrashReporting()
|
||||
"autoplay_video" -> prefsRepository.isAutoPlayVideoEnabled()
|
||||
"always_show_first_view_on_app_start" -> prefsRepository.isAlwaysShowFirstViewOnAppStartEnabled()
|
||||
"webview_debug" -> prefsRepository.isWebViewDebugEnabled()
|
||||
else -> throw IllegalArgumentException("No boolean found by this key: $key")
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +74,6 @@ class SettingsPresenterImpl @Inject constructor(
|
|||
"crash_reporting" -> prefsRepository.setCrashReporting(value)
|
||||
"autoplay_video" -> prefsRepository.setAutoPlayVideo(value)
|
||||
"always_show_first_view_on_app_start" -> prefsRepository.setAlwaysShowFirstViewOnAppStart(value)
|
||||
"webview_debug" -> prefsRepository.setWebViewDebugEnabled(value)
|
||||
else -> throw IllegalArgumentException("No boolean found by this key: $key")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package io.homeassistant.companion.android.settings.developer
|
||||
|
||||
import android.content.IntentSender
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import io.homeassistant.companion.android.settings.log.LogFragment
|
||||
import io.homeassistant.companion.android.settings.server.ServerChooserFragment
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DeveloperSettingsFragment : DeveloperSettingsView, PreferenceFragmentCompat() {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: DeveloperSettingsPresenter
|
||||
|
||||
private var threadDebugDialog: AlertDialog? = null
|
||||
private var threadIntentServer: Int = ServerManager.SERVER_ID_ACTIVE
|
||||
private var threadIntentDeviceOnly: Boolean = true
|
||||
|
||||
private val threadPermissionLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
|
||||
presenter.onThreadPermissionResult(requireContext(), result, threadIntentServer, threadIntentDeviceOnly)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
presenter.init(this)
|
||||
|
||||
preferenceManager.preferenceDataStore = presenter.getPreferenceDataStore()
|
||||
|
||||
setPreferencesFromResource(R.xml.preferences_developer, rootKey)
|
||||
|
||||
findPreference<Preference>("show_share_logs")?.setOnPreferenceClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.content, LogFragment::class.java, null)
|
||||
addToBackStack(getString(io.homeassistant.companion.android.common.R.string.log))
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
findPreference<Preference>("thread_debug")?.let {
|
||||
it.isVisible = presenter.appSupportsThread()
|
||||
it.setOnPreferenceClickListener {
|
||||
if (presenter.hasMultipleServers()) {
|
||||
parentFragmentManager.setFragmentResultListener(ServerChooserFragment.RESULT_KEY, this) { _, bundle ->
|
||||
if (bundle.containsKey(ServerChooserFragment.RESULT_SERVER)) {
|
||||
startThreadDebug(bundle.getInt(ServerChooserFragment.RESULT_SERVER))
|
||||
}
|
||||
parentFragmentManager.clearFragmentResultListener(ServerChooserFragment.RESULT_KEY)
|
||||
}
|
||||
ServerChooserFragment().show(parentFragmentManager, ServerChooserFragment.TAG)
|
||||
} else {
|
||||
startThreadDebug(ServerManager.SERVER_ID_ACTIVE)
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startThreadDebug(serverId: Int) {
|
||||
presenter.runThreadDebug(requireContext(), serverId)
|
||||
threadDebugDialog = AlertDialog.Builder(requireContext())
|
||||
.setMessage(commonR.string.thread_debug_active)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
threadDebugDialog?.show()
|
||||
}
|
||||
|
||||
override fun onThreadPermissionRequest(intent: IntentSender, serverId: Int, isDeviceOnly: Boolean) {
|
||||
threadIntentServer = serverId
|
||||
threadIntentDeviceOnly = isDeviceOnly
|
||||
threadPermissionLauncher.launch(IntentSenderRequest.Builder(intent).build())
|
||||
}
|
||||
|
||||
override fun onThreadDebugResult(result: String, success: Boolean?) {
|
||||
threadDebugDialog?.hide()
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(commonR.string.thread_debug)
|
||||
.setMessage("${if (success == true) "✅" else if (success == null) "⚠️" else "⛔"}\n\n$result")
|
||||
.setPositiveButton(commonR.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title = getString(commonR.string.troubleshooting)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presenter.onFinish()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.homeassistant.companion.android.settings.developer
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.preference.PreferenceDataStore
|
||||
|
||||
interface DeveloperSettingsPresenter {
|
||||
fun init(view: DeveloperSettingsView)
|
||||
fun getPreferenceDataStore(): PreferenceDataStore
|
||||
fun onFinish()
|
||||
|
||||
fun hasMultipleServers(): Boolean
|
||||
|
||||
fun appSupportsThread(): Boolean
|
||||
fun runThreadDebug(context: Context, serverId: Int)
|
||||
fun onThreadPermissionResult(context: Context, result: ActivityResult, serverId: Int, isDeviceOnly: Boolean)
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package io.homeassistant.companion.android.settings.developer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import io.homeassistant.companion.android.thread.ThreadManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
class DeveloperSettingsPresenterImpl @Inject constructor(
|
||||
private val prefsRepository: PrefsRepository,
|
||||
private val serverManager: ServerManager,
|
||||
private val threadManager: ThreadManager
|
||||
) : DeveloperSettingsPresenter, PreferenceDataStore() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DevSettingsPresenter"
|
||||
}
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
private lateinit var view: DeveloperSettingsView
|
||||
|
||||
override fun init(view: DeveloperSettingsView) {
|
||||
this.view = view
|
||||
}
|
||||
|
||||
override fun getPreferenceDataStore(): PreferenceDataStore = this
|
||||
|
||||
override fun onFinish() {
|
||||
mainScope.cancel()
|
||||
}
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean): Boolean = runBlocking {
|
||||
return@runBlocking when (key) {
|
||||
"webview_debug" -> prefsRepository.isWebViewDebugEnabled()
|
||||
else -> throw IllegalArgumentException("No boolean found by this key: $key")
|
||||
}
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
mainScope.launch {
|
||||
when (key) {
|
||||
"webview_debug" -> prefsRepository.setWebViewDebugEnabled(value)
|
||||
else -> throw IllegalArgumentException("No boolean found by this key: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasMultipleServers(): Boolean = serverManager.defaultServers.size > 1
|
||||
|
||||
override fun appSupportsThread(): Boolean = threadManager.appSupportsThread()
|
||||
|
||||
override fun runThreadDebug(context: Context, serverId: Int) {
|
||||
mainScope.launch {
|
||||
try {
|
||||
when (val syncResult = threadManager.syncPreferredDataset(context, serverId, this)) {
|
||||
is ThreadManager.SyncResult.ServerUnsupported ->
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_unsupported_server), false)
|
||||
is ThreadManager.SyncResult.OnlyOnServer -> {
|
||||
if (syncResult.imported) {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_imported), true)
|
||||
} else {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
}
|
||||
is ThreadManager.SyncResult.OnlyOnDevice -> {
|
||||
if (syncResult.exportIntent != null) {
|
||||
view.onThreadPermissionRequest(syncResult.exportIntent, serverId, true)
|
||||
} // else currently doesn't happen
|
||||
}
|
||||
is ThreadManager.SyncResult.AllHaveCredentials -> {
|
||||
if (syncResult.exportIntent != null) {
|
||||
view.onThreadPermissionRequest(syncResult.exportIntent, serverId, false)
|
||||
} else if (syncResult.matches == true) {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_match), true)
|
||||
} else {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
}
|
||||
is ThreadManager.SyncResult.NoneHaveCredentials ->
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_none), null)
|
||||
else ->
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception while syncing preferred Thread dataset", e)
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onThreadPermissionResult(
|
||||
context: Context,
|
||||
result: ActivityResult,
|
||||
serverId: Int,
|
||||
isDeviceOnly: Boolean
|
||||
) {
|
||||
mainScope.launch {
|
||||
try {
|
||||
val submitted = threadManager.sendThreadDatasetExportResult(result, serverId)
|
||||
if (submitted != null) {
|
||||
if (isDeviceOnly) {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_exported), true)
|
||||
} else {
|
||||
// If we got permission while both had a dataset, the device prefers a different network
|
||||
val out = "${context.getString(commonR.string.thread_debug_result_mismatch)} ${context.getString(commonR.string.thread_debug_result_mismatch_detail, submitted)}"
|
||||
view.onThreadDebugResult(out, null)
|
||||
}
|
||||
} else {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
view.onThreadDebugResult(context.getString(commonR.string.thread_debug_result_error), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.homeassistant.companion.android.settings.developer
|
||||
|
||||
import android.content.IntentSender
|
||||
|
||||
interface DeveloperSettingsView {
|
||||
fun onThreadPermissionRequest(intent: IntentSender, serverId: Int, isDeviceOnly: Boolean)
|
||||
fun onThreadDebugResult(result: String, success: Boolean?)
|
||||
}
|
|
@ -8,6 +8,15 @@ import kotlinx.coroutines.CoroutineScope
|
|||
|
||||
interface ThreadManager {
|
||||
|
||||
sealed class SyncResult {
|
||||
object AppUnsupported : SyncResult()
|
||||
object ServerUnsupported : SyncResult()
|
||||
class OnlyOnServer(val imported: Boolean) : SyncResult()
|
||||
class OnlyOnDevice(val exportIntent: IntentSender?) : SyncResult()
|
||||
class AllHaveCredentials(val matches: Boolean?, val exportIntent: IntentSender?) : SyncResult()
|
||||
object NoneHaveCredentials : SyncResult()
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the app on this device supports Thread credential management.
|
||||
*/
|
||||
|
@ -22,14 +31,14 @@ 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 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
|
||||
* @return [SyncResult] with details of the sync operation, which may include an [IntentSender]
|
||||
* if permission is required to import the device dataset
|
||||
*/
|
||||
suspend fun syncPreferredDataset(
|
||||
context: Context,
|
||||
serverId: Int,
|
||||
scope: CoroutineScope
|
||||
): IntentSender?
|
||||
): SyncResult
|
||||
|
||||
/**
|
||||
* Get the preferred Thread dataset from the server.
|
||||
|
@ -55,6 +64,7 @@ interface ThreadManager {
|
|||
/**
|
||||
* Process the result from [syncPreferredDataset] or [getPreferredDatasetFromDevice]'s intent
|
||||
* and add the Thread dataset, if any, to the server.
|
||||
* @return Network name that was sent and accepted, or `null` if not sent or accepted
|
||||
*/
|
||||
suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int)
|
||||
suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int): String?
|
||||
}
|
||||
|
|
|
@ -333,7 +333,11 @@ class WebViewPresenterImpl @Inject constructor(
|
|||
|
||||
mainScope.launch {
|
||||
val deviceThreadIntent = try {
|
||||
threadUseCase.syncPreferredDataset(context, serverId, this)
|
||||
when (val result = threadUseCase.syncPreferredDataset(context, serverId, this)) {
|
||||
is ThreadManager.SyncResult.OnlyOnDevice -> result.exportIntent
|
||||
is ThreadManager.SyncResult.AllHaveCredentials -> result.exportIntent
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to sync preferred Thread dataset, continuing", e)
|
||||
null
|
||||
|
|
5
app/src/main/res/drawable/ic_bug_report.xml
Normal file
5
app/src/main/res/drawable/ic_bug_report.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="@color/colorAccent"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_thread.xml
Normal file
5
app/src/main/res/drawable/ic_thread.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:viewportHeight="165"
|
||||
android:viewportWidth="165" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@color/colorAccent" android:pathData="M82.5,0C37.01,0 0,37.01 0,82.5c0,45.18 36.51,81.98 81.57,82.48V82.57l-27,-0c-8.02,0 -14.55,6.53 -14.55,14.56c0,8.02 6.53,14.55 14.55,14.55v17.98c-17.94,0 -32.53,-14.6 -32.53,-32.53c0,-17.94 14.6,-32.54 32.53,-32.54l27,0v-9.1c0,-14.93 12.15,-27.08 27.08,-27.08c14.93,0 27.08,12.15 27.08,27.08c0,14.93 -12.15,27.08 -27.08,27.08l-9.1,-0v80.64C136.89,155.33 165,122.14 165,82.5C165,37.01 127.99,0 82.5,0z"/>
|
||||
<path android:fillColor="@color/colorAccent" android:pathData="M117.75,55.49c0,-5.02 -4.08,-9.1 -9.1,-9.1c-5.01,0 -9.1,4.08 -9.1,9.1v9.1l9.1,0C113.67,64.59 117.75,60.51 117.75,55.49z"/>
|
||||
</vector>
|
|
@ -67,11 +67,6 @@
|
|||
android:icon="@drawable/ic_home_variant_outline"
|
||||
android:title="@string/always_show_first_view_on_app_start"
|
||||
android:summary="@string/always_show_first_view_on_app_start_summary" />
|
||||
<SwitchPreference
|
||||
android:key="webview_debug"
|
||||
android:icon="@drawable/ic_android_debug_bridge"
|
||||
android:title="@string/remote_debugging"
|
||||
android:summary="@string/remote_debugging_summary" />
|
||||
<Preference
|
||||
android:key="nfc_tags"
|
||||
android:icon="@drawable/ic_nfc"
|
||||
|
@ -176,10 +171,10 @@
|
|||
android:data="https://companion.home-assistant.io" />
|
||||
</Preference>
|
||||
<Preference
|
||||
android:key="show_share_logs"
|
||||
android:icon="@drawable/ic_notes"
|
||||
android:title="@string/show_share_logs"
|
||||
android:summary="@string/show_share_logs_summary" />
|
||||
android:key="developer"
|
||||
android:icon="@drawable/ic_bug_report"
|
||||
android:title="@string/troubleshooting"
|
||||
android:summary="@string/troubleshooting_summary"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/app_version_info">
|
||||
|
|
18
app/src/main/res/xml/preferences_developer.xml
Normal file
18
app/src/main/res/xml/preferences_developer.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:key="show_share_logs"
|
||||
android:icon="@drawable/ic_notes"
|
||||
android:title="@string/show_share_logs"
|
||||
android:summary="@string/show_share_logs_summary" />
|
||||
<SwitchPreference
|
||||
android:key="webview_debug"
|
||||
android:icon="@drawable/ic_android_debug_bridge"
|
||||
android:title="@string/remote_debugging"
|
||||
android:summary="@string/remote_debugging_summary" />
|
||||
<Preference
|
||||
android:key="thread_debug"
|
||||
android:icon="@drawable/ic_thread"
|
||||
android:title="@string/thread_debug"
|
||||
android:summary="@string/thread_debug_summary"/>
|
||||
</PreferenceScreen>
|
|
@ -20,7 +20,7 @@ class ThreadManagerImpl @Inject constructor() : ThreadManager {
|
|||
context: Context,
|
||||
serverId: Int,
|
||||
scope: CoroutineScope
|
||||
): IntentSender? = null
|
||||
): ThreadManager.SyncResult = ThreadManager.SyncResult.AppUnsupported
|
||||
|
||||
override suspend fun getPreferredDatasetFromServer(serverId: Int): ThreadDatasetResponse? = null
|
||||
|
||||
|
@ -30,5 +30,5 @@ class ThreadManagerImpl @Inject constructor() : ThreadManager {
|
|||
throw IllegalStateException("Thread is not supported with the minimal flavor")
|
||||
}
|
||||
|
||||
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int) { }
|
||||
override suspend fun sendThreadDatasetExportResult(result: ActivityResult, serverId: Int): String? = null
|
||||
}
|
||||
|
|
|
@ -782,6 +782,8 @@
|
|||
<string name="template_widget_default">Enter Template Here</string>
|
||||
<string name="template_widget_desc">Render any template with HTML formatting</string>
|
||||
<string name="template_widget">Template Widget</string>
|
||||
<string name="troubleshooting">Troubleshooting</string>
|
||||
<string name="troubleshooting_summary">Logs and other tools to diagnose issues</string>
|
||||
<string name="themes_option_label_dark">Dark</string>
|
||||
<string name="themes_option_label_light">Light</string>
|
||||
<string name="themes_option_label_system">Follow System Settings</string>
|
||||
|
@ -1050,6 +1052,17 @@
|
|||
<string name="sensor_description_daily_calories">The total number of calories over a day (including both BMR and active calories), where the previous day ends and a new day begins at 12:00 AM local time.</string>
|
||||
<string name="sensor_name_daily_steps">Daily Steps</string>
|
||||
<string name="sensor_description_daily_steps">The total step count over a day, where the previous day ends and a new day begins at 12:00 AM local time.</string>
|
||||
<string name="thread_debug">Sync Thread credentials</string>
|
||||
<string name="thread_debug_active">Syncing…</string>
|
||||
<string name="thread_debug_result_error">An unexpected error occurred while syncing</string>
|
||||
<string name="thread_debug_result_exported">Added network from this device to Home Assistant</string>
|
||||
<string name="thread_debug_result_imported">Added network from Home Assistant to this device</string>
|
||||
<string name="thread_debug_result_match">Home Assistant and this device use the same network</string>
|
||||
<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_unsupported_server">The Home Assistant server does not support Thread</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>
|
||||
<string name="no_results">No results yet</string>
|
||||
|
|
Loading…
Reference in a new issue