From cf72e28727410e82aa187e91b954196eeac03b29 Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 24 Sep 2022 15:49:56 +0200 Subject: [PATCH] Fix app crash if Bluetooth adapter is null and we can't retrieve the list of bonded devices in settings. --- .../capod/common/bluetooth/BleScanner.kt | 4 +-- .../common/bluetooth/BluetoothManager2.kt | 11 +++--- .../reaction/core/autoconnect/AutoConnect.kt | 3 +- .../capod/common/uix/PreferenceFragment2.kt | 15 -------- .../capod/common/uix/PreferenceFragment3.kt | 35 +++++++++++++++++++ .../general/GeneralSettingsFragment.kt | 6 ++-- .../general/GeneralSettingsFragmentVM.kt | 8 ++--- .../general/debug/DebugSettingsFragment.kt | 6 ++-- .../ui/settings/support/SupportFragment.kt | 6 ++-- .../reaction/ui/ReactionSettingsFragment.kt | 15 ++++---- .../reaction/ui/ReactionSettingsFragmentVM.kt | 6 ++-- 11 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment3.kt diff --git a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt index 2f57b157..a425804d 100644 --- a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt +++ b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt @@ -35,7 +35,7 @@ class BleScanner @Inject constructor( log(TAG, VERBOSE) { "scan(filters=$filters, scannerMode=$scannerMode, compatMode=$compatMode)" } if (compatMode) log(TAG, WARN) { "Using compatibilityMode!" } - val adapter = bluetoothManager.adapter + val adapter = bluetoothManager.adapter ?: throw IllegalStateException("Bluetooth adapter unavailable") val supportsOffloadFiltering = adapter.isOffloadedFilteringSupported.also { log(TAG, if (it) DEBUG else WARN) { "isOffloadedFilteringSupported=$it" } @@ -45,7 +45,7 @@ class BleScanner @Inject constructor( log(TAG, if (it) DEBUG else WARN) { "isOffloadedScanBatchingSupported=$it" } } && !compatMode - val scanner = bluetoothManager.scanner + val scanner = bluetoothManager.scanner ?: throw IllegalStateException("BLE scanner unavailable") val callback = object : ScanCallback() { var lastScanAt = System.currentTimeMillis() diff --git a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt index f6c3215b..5da8d40c 100644 --- a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt +++ b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt @@ -30,12 +30,11 @@ class BluetoothManager2 @Inject constructor( private val dispatcherProvider: DispatcherProvider, ) { - val adapter: BluetoothAdapter + val adapter: BluetoothAdapter? get() = manager.adapter - val scanner: BluetoothLeScanner - get() = adapter.bluetoothLeScanner - ?: throw IllegalStateException("Bluetooth is disabled or permissiong missing") + val scanner: BluetoothLeScanner? + get() = adapter?.bluetoothLeScanner val isBluetoothEnabled: Flow = callbackFlow { send(manager.adapter?.isEnabled ?: false) @@ -158,7 +157,9 @@ class BluetoothManager2 @Inject constructor( } } - fun bondedDevices(): Set = adapter.bondedDevices + fun bondedDevices(): Flow> = flow { + emit(adapter?.bondedDevices ?: throw IllegalStateException("Bluetooth adapter unavailable")) + } suspend fun nudgeConnection(device: BluetoothDevice): Boolean = getBluetoothProfile().map { bluetoothProfile -> try { diff --git a/app-common/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt b/app-common/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt index 6f90e981..7fc7a8b6 100644 --- a/app-common/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt +++ b/app-common/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt @@ -46,7 +46,8 @@ class AutoConnect @Inject constructor( return@map } - val bondedDevice = bluetoothManager.bondedDevices().firstOrNull { it.address == mainDeviceAddr } + val bondedDevice = bluetoothManager.bondedDevices().first().firstOrNull { it.address == mainDeviceAddr } + if (bondedDevice == null) { log(TAG, WARN) { "No bonded device matches $mainDeviceAddr" } return@map diff --git a/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment2.kt b/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment2.kt index 98f23401..08254b4a 100644 --- a/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment2.kt +++ b/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment2.kt @@ -10,9 +10,7 @@ import androidx.annotation.MenuRes import androidx.annotation.XmlRes import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment -import androidx.lifecycle.LiveData import androidx.preference.PreferenceFragmentCompat -import androidx.viewbinding.ViewBinding import eu.darken.capod.common.preferences.Settings import eu.darken.capod.main.ui.settings.SettingsFragment @@ -69,17 +67,4 @@ abstract class PreferenceFragment2 } } } - - inline fun LiveData.observe2( - crossinline callback: (T) -> Unit - ) { - observe(viewLifecycleOwner) { callback.invoke(it) } - } - - inline fun LiveData.observe2( - ui: VB, - crossinline callback: VB.(T) -> Unit - ) { - observe(viewLifecycleOwner) { callback.invoke(ui, it) } - } } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment3.kt b/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment3.kt new file mode 100644 index 00000000..72e99556 --- /dev/null +++ b/app/src/main/java/eu/darken/capod/common/uix/PreferenceFragment3.kt @@ -0,0 +1,35 @@ +package eu.darken.capod.common.uix + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.LiveData +import androidx.viewbinding.ViewBinding +import eu.darken.capod.common.error.asErrorDialogBuilder + +abstract class PreferenceFragment3 : PreferenceFragment2() { + + abstract val vm: ViewModel3 + + var onErrorEvent: ((Throwable) -> Boolean)? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + vm.errorEvents.observe2 { + val showDialog = onErrorEvent?.invoke(it) ?: true + if (showDialog) it.asErrorDialogBuilder(requireContext()).show() + } + } + + inline fun LiveData.observe2( + crossinline callback: (T) -> Unit + ) { + observe(viewLifecycleOwner) { callback.invoke(it) } + } + + inline fun LiveData.observe2( + ui: VB, + crossinline callback: VB.(T) -> Unit + ) { + observe(viewLifecycleOwner) { callback.invoke(ui, it) } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragment.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragment.kt index 56d8f662..15fbe7d9 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragment.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragment.kt @@ -10,7 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint import eu.darken.capod.R import eu.darken.capod.common.bluetooth.ScannerMode import eu.darken.capod.common.preferences.PercentSliderPreference -import eu.darken.capod.common.uix.PreferenceFragment2 +import eu.darken.capod.common.uix.PreferenceFragment3 import eu.darken.capod.common.upgrade.UpgradeRepo import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode @@ -19,9 +19,9 @@ import javax.inject.Inject @Keep @AndroidEntryPoint -class GeneralSettingsFragment : PreferenceFragment2() { +class GeneralSettingsFragment : PreferenceFragment3() { - private val vm: GeneralSettingsFragmentVM by viewModels() + override val vm: GeneralSettingsFragmentVM by viewModels() @Inject lateinit var generalSettings: GeneralSettings @Inject lateinit var upgradeRepo: UpgradeRepo diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragmentVM.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragmentVM.kt index 8f496c1e..f5131f91 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragmentVM.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/general/GeneralSettingsFragmentVM.kt @@ -6,7 +6,7 @@ import eu.darken.capod.common.bluetooth.BluetoothManager2 import eu.darken.capod.common.coroutine.DispatcherProvider import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.uix.ViewModel3 -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject @HiltViewModel @@ -16,9 +16,9 @@ class GeneralSettingsFragmentVM @Inject constructor( private val bluetoothManager: BluetoothManager2, ) : ViewModel3(dispatcherProvider) { - val bondedDevices = flow { - emit(bluetoothManager.bondedDevices().toList()) - }.asLiveData2() + val bondedDevices = bluetoothManager.bondedDevices() + .map { it.toList() } + .asLiveData2() companion object { private val TAG = logTag("Settings", "General", "VM") diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/general/debug/DebugSettingsFragment.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/general/debug/DebugSettingsFragment.kt index cf031694..f37d013b 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/general/debug/DebugSettingsFragment.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/general/debug/DebugSettingsFragment.kt @@ -9,14 +9,14 @@ import dagger.hilt.android.AndroidEntryPoint import eu.darken.capod.R import eu.darken.capod.common.debug.autoreport.DebugSettings import eu.darken.capod.common.observe2 -import eu.darken.capod.common.uix.PreferenceFragment2 +import eu.darken.capod.common.uix.PreferenceFragment3 import javax.inject.Inject @Keep @AndroidEntryPoint -class DebugSettingsFragment : PreferenceFragment2() { +class DebugSettingsFragment : PreferenceFragment3() { - private val vm: DebugSettingsFragmentVM by viewModels() + override val vm: DebugSettingsFragmentVM by viewModels() @Inject lateinit var debugSettings: DebugSettings diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportFragment.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportFragment.kt index 38821268..6fd22a2c 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportFragment.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportFragment.kt @@ -10,15 +10,15 @@ import dagger.hilt.android.AndroidEntryPoint import eu.darken.capod.R import eu.darken.capod.common.ClipboardHelper import eu.darken.capod.common.observe2 -import eu.darken.capod.common.uix.PreferenceFragment2 +import eu.darken.capod.common.uix.PreferenceFragment3 import eu.darken.capod.main.core.GeneralSettings import javax.inject.Inject @Keep @AndroidEntryPoint -class SupportFragment : PreferenceFragment2() { +class SupportFragment : PreferenceFragment3() { - private val vm: SupportFragmentVM by viewModels() + override val vm: SupportFragmentVM by viewModels() override val preferenceFile: Int = R.xml.preferences_support @Inject lateinit var generalSettings: GeneralSettings diff --git a/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragment.kt b/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragment.kt index 8612ea5f..2863e1c9 100644 --- a/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragment.kt +++ b/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragment.kt @@ -1,5 +1,6 @@ package eu.darken.capod.reaction.ui +import android.bluetooth.BluetoothDevice import android.os.Bundle import android.view.View import androidx.annotation.Keep @@ -10,7 +11,7 @@ import androidx.preference.ListPreference import androidx.preference.Preference import dagger.hilt.android.AndroidEntryPoint import eu.darken.capod.R -import eu.darken.capod.common.uix.PreferenceFragment2 +import eu.darken.capod.common.uix.PreferenceFragment3 import eu.darken.capod.common.upgrade.UpgradeRepo import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode @@ -21,9 +22,9 @@ import javax.inject.Inject @Keep @AndroidEntryPoint -class ReactionSettingsFragment : PreferenceFragment2() { +class ReactionSettingsFragment : PreferenceFragment3() { - private val vm: ReactionSettingsFragmentVM by viewModels() + override val vm: ReactionSettingsFragmentVM by viewModels() @Inject lateinit var generalSettings: GeneralSettings @Inject lateinit var reactionSettings: ReactionSettings @@ -35,6 +36,7 @@ class ReactionSettingsFragment : PreferenceFragment2() { override val preferenceFile: Int = R.xml.preferences_reactions private var isPro: Boolean = false + private var bondedDevices: List = emptyList() private val autoConnectConditionPref by lazy { findPreference(settings.autoConnectCondition.key)!! } override fun onPreferencesCreated() { @@ -66,10 +68,9 @@ class ReactionSettingsFragment : PreferenceFragment2() { preference.isChecked = false return true } else if (generalSettings.mainDeviceAddress.value == null) { - val devices = vm.bondedDevices DeviceSelectionDialogFactory(requireContext()).create( - devices = devices, - current = devices.firstOrNull { it.address == generalSettings.mainDeviceAddress.value } + devices = bondedDevices, + current = bondedDevices.firstOrNull { it.address == generalSettings.mainDeviceAddress.value } ) { selected -> generalSettings.mainDeviceAddress.value = selected?.address if (selected != null) preference.isChecked = true @@ -95,6 +96,8 @@ class ReactionSettingsFragment : PreferenceFragment2() { vm.isPro.observe2 { isPro = it } + vm.bondedDevices.observe2 { bondedDevices = it } + super.onViewCreated(view, savedInstanceState) } diff --git a/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragmentVM.kt b/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragmentVM.kt index 2ed73d3b..13529c93 100644 --- a/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragmentVM.kt +++ b/app/src/main/java/eu/darken/capod/reaction/ui/ReactionSettingsFragmentVM.kt @@ -18,10 +18,12 @@ class ReactionSettingsFragmentVM @Inject constructor( private val upgradeRepo: UpgradeRepo, ) : ViewModel3(dispatcherProvider) { - val bondedDevices = bluetoothManager.bondedDevices().toList() - val isPro = upgradeRepo.upgradeInfo.map { it.isPro }.asLiveData2() + val bondedDevices = bluetoothManager.bondedDevices() + .map { it.toList() } + .asLiveData2() + companion object { private val TAG = logTag("Settings", "Reaction", "VM") }