From 53e25967dbb44f36cce9383ab359e4b8491cecb6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 13 Feb 2020 19:54:05 -0800 Subject: [PATCH] bt scan kinda works --- TODO.md | 3 +- .../mesh/service/RadioInterfaceService.kt | 27 +++++-- .../com/geeksville/mesh/ui/BTScanScreen.kt | 70 ++++++++++++------- 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/TODO.md b/TODO.md index d3c8e837c..4426fb813 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ MVP features required for first public alpha * if no radio is selected, launch app on the radio select screen -* warn user to bt pair +* when we select a new radio, restart the service * show bt scan progress centered and towards the bottom of the screen * get rid of green bar at top * change titlebar based off which screen we are showing @@ -105,3 +105,4 @@ Don't leave device discoverable. Don't let unpaired users do things with device * use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state * all chat in the app defaults to group chat * start bt receive on boot +* warn user to bt pair \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 5628b6b8e..69a69d7a3 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -135,9 +135,26 @@ class RadioInterfaceService : Service(), Logging { private const val DEVADDR_KEY = "devAddr" + /// Get our bluetooth adapter (should always succeed except on emulator + private fun getBluetoothAdapter(context: Context): BluetoothAdapter? { + val bluetoothManager = + context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + return bluetoothManager.adapter + } + /// Return the device we are configured to use, or null for none - fun getBondedDeviceAddress(context: Context) = - getPrefs(context).getString(DEVADDR_KEY, null) + fun getBondedDeviceAddress(context: Context): String? { + + val allPaired = + getBluetoothAdapter(context)?.bondedDevices.orEmpty().map { it.address }.toSet() + + // If the user has unpaired our device, treat things as if we don't have one + val addr = getPrefs(context).getString(DEVADDR_KEY, null) + return if (!allPaired.contains(addr)) + null + else + addr + } fun setBondedDeviceAddress(context: Context, addr: String?) = getPrefs(context).edit(commit = true) { @@ -148,10 +165,6 @@ class RadioInterfaceService : Service(), Logging { } } - private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { - val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - bluetoothManager.adapter - } // Both of these are created in onCreate() private var safe: SafeBluetooth? = null @@ -270,7 +283,7 @@ class RadioInterfaceService : Service(), Logging { else { // Note: this call does no comms, it just creates the device object (even if the // device is off/not connected) - val device = bluetoothAdapter?.getRemoteDevice(address) + val device = getBluetoothAdapter(this)?.getRemoteDevice(address) if (device != null) { info("Creating radio interface service. device=$address") diff --git a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt index 37fb61ff3..24887582c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt @@ -1,5 +1,6 @@ package com.geeksville.mesh.ui +import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanFilter @@ -12,6 +13,8 @@ import androidx.ui.core.ContextAmbient import androidx.ui.core.Text import androidx.ui.layout.Column import androidx.ui.material.CircularProgressIndicator +import androidx.ui.material.EmphasisLevels +import androidx.ui.material.ProvideEmphasis import androidx.ui.material.RadioGroup import androidx.ui.tooling.preview.Preview import com.geeksville.android.Logging @@ -27,7 +30,7 @@ object ScanState { } @Model -data class BTScanEntry(val name: String, val macAddress: String) { +data class BTScanEntry(val name: String, val macAddress: String, val bonded: Boolean) { val isSelected get() = macAddress == ScanState.selectedMacAddr } @@ -45,6 +48,7 @@ fun BTScanScreen() { ScanState.selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context) fun changeSelection(newAddr: String) { + BTLog.info("Changing BT device to $newAddr") ScanState.selectedMacAddr = newAddr RadioInterfaceService.setBondedDeviceAddress(context, newAddr) } @@ -55,8 +59,8 @@ fun BTScanScreen() { BTLog.warn("No bluetooth adapter. Running under emulation?") val testnodes = listOf( - BTScanEntry("Meshtastic_ab12", "xx"), - BTScanEntry("Meshtastic_32ac", "xb") + BTScanEntry("Meshtastic_ab12", "xx", false), + BTScanEntry("Meshtastic_32ac", "xb", true) ) devices.putAll(testnodes.map { it.macAddress to it }) @@ -81,13 +85,20 @@ fun BTScanScreen() { override fun onScanResult(callbackType: Int, result: ScanResult) { val addr = result.device.address - BTLog.debug("onScanResult ${addr}") - devices[addr] = - BTScanEntry(result.device.name, addr) + // prevent logspam because weill get get lots of redundant scan results + if (!devices.contains(addr)) { + val entry = BTScanEntry( + result.device.name, + addr, + result.device.bondState == BluetoothDevice.BOND_BONDED + ) + BTLog.debug("onScanResult ${entry}") + devices[addr] = entry - // If nothing was selected, by default select the first thing we see - if (ScanState.selectedMacAddr == null) - changeSelection(addr) + // If nothing was selected, by default select the first thing we see + if (ScanState.selectedMacAddr == null && entry.bonded) + changeSelection(addr) + } } } @@ -116,36 +127,47 @@ fun BTScanScreen() { if (ScanState.errorText != null) { Text("An unexpected error was encountered. Please file a bug on our github: ${ScanState.errorText}") } else { - if (devices.isEmpty()) + if (devices.isEmpty()) { Text("Looking for Meshtastic devices... (zero found)") - else { - val allPaired = bluetoothAdapter?.bondedDevices.orEmpty().map { it.address } - // Only let user select paired devices + CircularProgressIndicator() // Show that we are searching still + } else { + // val allPaired = bluetoothAdapter?.bondedDevices.orEmpty().map { it.address }.toSet() + + /* Only let user select paired devices val paired = devices.values.filter { allPaired.contains(it.macAddress) } if (paired.size < devices.size) { Text( "Warning: there are nearby Meshtastic devices that are not paired with this phone. Before you can select a device, you will need to pair it in Bluetooth Settings." ) - } + } */ RadioGroup { Column { - paired.forEach { + devices.values.forEach { // disabled pending https://issuetracker.google.com/issues/149528535 - //ProvideEmphasis(emphasis = if (allPaired.contains(it.macAddress)) EmphasisLevels().medium else EmphasisLevels().disabled) { - RadioGroupTextItem( - selected = (it.isSelected), - onSelect = { changeSelection(it.macAddress) }, - text = it.name - ) - //} + ProvideEmphasis(emphasis = if (it.bonded) EmphasisLevels().high else EmphasisLevels().disabled) { + RadioGroupTextItem( + selected = (it.isSelected), + onSelect = { + // If the device is paired, let user select it, otherwise start the pairing flow + if (it.bonded) + changeSelection(it.macAddress) + else { + BTLog.info("Starting bonding for $it") + + // We ignore missing BT adapters, because it lets us run on the emulator + bluetoothAdapter?.getRemoteDevice(it.macAddress) + ?.createBond() + } + }, + text = it.name + ) + } } } } } - - CircularProgressIndicator() // Show that we are searching still } } }