diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index 88d3b1ae6..a5c4b86f8 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -29,6 +29,7 @@ import com.geeksville.mesh.util.anonymize import com.hoho.android.usbserial.driver.UsbSerialDriver import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import java.util.regex.Pattern @@ -45,11 +46,17 @@ class BTScanModel @Inject constructor( private val context: Context get() = application.applicationContext val devices = MutableLiveData>(mutableMapOf()) + private val bleDevices = MutableLiveData>(listOf()) + private val usbDevices = MutableLiveData>(mapOf()) init { - bluetoothRepository.state.value.bondedDevices.onEach { - setupScan() // TODO clean up device list updates - }.launchIn(viewModelScope) + combine( + bluetoothRepository.state.value.bondedDevices, + usbRepository.serialDevicesWithDrivers + ) { ble, usb -> + bleDevices.value = ble + usbDevices.value = usb + }.onEach { setupScan() }.launchIn(viewModelScope) debug("BTScanModel created") } @@ -166,7 +173,7 @@ class BTScanModel @Inject constructor( /** * returns true if we could start scanning, false otherwise */ - fun setupScan(): Boolean { + private fun setupScan(): Boolean { selectedAddress = radioInterfaceService.getDeviceAddress() return if (MockInterface.addressValid(context, usbRepository, "")) { @@ -192,24 +199,30 @@ class BTScanModel @Inject constructor( true } else { if (scanner == null) { - // Clear the old device list - devices.value?.clear() + val newDevs = mutableMapOf() + + fun addDevice(entry: DeviceListEntry) { + newDevs[entry.fullAddress] = entry + } // Include a placeholder for "None" addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) // Include paired Bluetooth devices - addBluetoothDevices() + bleDevices.value?.forEach { + addDevice(BLEDeviceListEntry(it)) + } // Include Network Service Discovery nsdRepository.resolvedList?.forEach { service -> addDevice(TCPDeviceListEntry(service)) } - val serialDevices by lazy { usbRepository.serialDevicesWithDrivers.value } - serialDevices.forEach { (_, d) -> + usbDevices.value?.forEach { (_, d) -> addDevice(USBDeviceListEntry(context.usbManager, d)) } + + devices.value = newDevs } else { debug("scan already running") } @@ -271,15 +284,6 @@ class BTScanModel @Inject constructor( } } - @SuppressLint("MissingPermission") - private fun addBluetoothDevices() { - bluetoothRepository.getBondedDevices() - ?.filter { it.name != null && it.name.matches(Regex(BLE_NAME_PATTERN)) } - ?.forEach { - addDevice(BLEDeviceListEntry(it)) - } - } - private val _spinner = MutableLiveData(false) val spinner: LiveData get() = _spinner @@ -355,7 +359,7 @@ class BTScanModel @Inject constructor( } companion object { - const val BLE_NAME_PATTERN = "^.*_([0-9a-fA-F]{4})$" + const val BLE_NAME_PATTERN = BluetoothRepository.BLE_NAME_PATTERN const val ACTION_USB_PERMISSION = "com.geeksville.mesh.USB_PERMISSION" } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt index a827e011a..7cfbfa0c8 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt @@ -66,13 +66,6 @@ class BluetoothRepository @Inject constructor( ?.bluetoothLeScanner } - @SuppressLint("MissingPermission") - fun getBondedDevices(): Set? { - return bluetoothAdapterLazy.get() - ?.takeIf { application.hasBluetoothPermission() } - ?.bondedDevices - } - @SuppressLint("MissingPermission") internal suspend fun updateBluetoothState() { val newState: BluetoothState = bluetoothAdapterLazy.get()?.takeIf { @@ -96,16 +89,18 @@ class BluetoothRepository @Inject constructor( * Creates a cold Flow used to obtain the set of bonded devices. */ @SuppressLint("MissingPermission") // Already checked prior to calling - private suspend fun createBondedDevicesFlow(adapter: BluetoothAdapter): Flow> { - return flow> { + private suspend fun createBondedDevicesFlow(adapter: BluetoothAdapter): Flow> { + return flow> { + val devices = adapter.bondedDevices ?: emptySet() while (true) { - emit(adapter.bondedDevices ?: emptySet()) + emit(devices.filter { it.name != null && it.name.matches(Regex(BLE_NAME_PATTERN)) }) delay(REFRESH_DELAY_MS) } }.flowOn(dispatchers.default).distinctUntilChanged() } companion object { + const val BLE_NAME_PATTERN = "^.*_([0-9a-fA-F]{4})$" const val REFRESH_DELAY_MS = 1000L } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothState.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothState.kt index 579c575f4..4aa4ceaac 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothState.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothState.kt @@ -13,5 +13,5 @@ data class BluetoothState( /** If we have adequate permissions and bluetooth is enabled */ val enabled: Boolean = false, /** If enabled, a cold flow of the currently bonded devices */ - val bondedDevices: Flow> = flowOf(emptySet()) + val bondedDevices: Flow> = flowOf(emptyList()) ) diff --git a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt index 384afe861..c65f80ca8 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import javax.inject.Inject import javax.inject.Singleton -import kotlin.collections.ArrayList @Singleton class NsdRepository @Inject constructor( @@ -21,7 +20,7 @@ class NsdRepository @Inject constructor( ) : Logging { private val resolveQueue = Semaphore(1) - private var hostsList: ArrayList? = ArrayList() + private var hostsList: ArrayList? = null val resolvedList: List? get() = hostsList @@ -91,7 +90,7 @@ class NsdRepository @Inject constructor( companion object { //To find all the available networks SERVICE_TYPE = "_services._dns-sd._udp" - const val SERVICE_TYPE = "_http._tcp." + const val SERVICE_TYPE = "_https._tcp." const val serviceName = "Meshtastic" } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index d6e333a6a..23e2bfb39 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -712,8 +712,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { override fun onResume() { super.onResume() - scanModel.setupScan() - // system permissions might have changed while we were away binding.provideLocationCheckbox.isChecked = requireContext().hasBackgroundPermission() && (model.provideLocation.value ?: false)