Meshtastic-Android/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt

158 lines
5.5 KiB
Kotlin
Raw Normal View History

2020-02-13 09:25:39 -08:00
package com.geeksville.mesh.ui
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.os.ParcelUuid
2020-02-13 19:02:40 -08:00
import androidx.compose.*
import androidx.compose.frames.modelMapOf
2020-02-13 09:25:39 -08:00
import androidx.ui.core.ContextAmbient
import androidx.ui.core.Text
import androidx.ui.layout.Column
2020-02-13 19:02:40 -08:00
import androidx.ui.material.CircularProgressIndicator
import androidx.ui.material.RadioGroup
2020-02-13 09:25:39 -08:00
import androidx.ui.tooling.preview.Preview
import com.geeksville.android.Logging
import com.geeksville.mesh.service.RadioInterfaceService
object BTLog : Logging
2020-02-13 19:02:40 -08:00
@Model
object ScanState {
var selectedMacAddr: String? = null
var errorText: String? = null
}
@Model
data class BTScanEntry(val name: String, val macAddress: String) {
val isSelected get() = macAddress == ScanState.selectedMacAddr
}
2020-02-13 09:25:39 -08:00
@Composable
fun BTScanScreen() {
val context = ambient(ContextAmbient)
/// Note: may be null on platforms without a bluetooth driver (ie. the emulator)
val bluetoothAdapter =
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
2020-02-13 19:02:40 -08:00
val devices = modelMapOf<String, BTScanEntry>()
ScanState.selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context)
fun changeSelection(newAddr: String) {
ScanState.selectedMacAddr = newAddr
RadioInterfaceService.setBondedDeviceAddress(context, newAddr)
}
2020-02-13 09:25:39 -08:00
onActive {
2020-02-13 19:02:40 -08:00
if (bluetoothAdapter == null) {
2020-02-13 09:25:39 -08:00
BTLog.warn("No bluetooth adapter. Running under emulation?")
2020-02-13 19:02:40 -08:00
val testnodes = listOf(
BTScanEntry("Meshtastic_ab12", "xx"),
BTScanEntry("Meshtastic_32ac", "xb")
)
devices.putAll(testnodes.map { it.macAddress to it })
// If nothing was selected, by default select the first thing we see
if (ScanState.selectedMacAddr == null)
changeSelection(testnodes.first().macAddress)
} else {
2020-02-13 09:25:39 -08:00
val scanner = bluetoothAdapter.bluetoothLeScanner
2020-02-13 19:02:40 -08:00
// ScanState.scanner = scanner
2020-02-13 09:25:39 -08:00
val scanCallback = object : ScanCallback() {
override fun onScanFailed(errorCode: Int) {
2020-02-13 19:02:40 -08:00
val msg = "Unexpected bluetooth scan failure: $errorCode"
ScanState.errorText = msg
BTLog.reportError(msg)
2020-02-13 09:25:39 -08:00
}
// For each device that appears in our scan, ask for its GATT, when the gatt arrives,
// check if it is an eligable device and store it in our list of candidates
// if that device later disconnects remove it as a candidate
override fun onScanResult(callbackType: Int, result: ScanResult) {
2020-02-13 19:02:40 -08:00
val addr = result.device.address
BTLog.debug("onScanResult ${addr}")
devices[addr] =
BTScanEntry(result.device.name, addr)
2020-02-13 09:25:39 -08:00
2020-02-13 19:02:40 -08:00
// If nothing was selected, by default select the first thing we see
if (ScanState.selectedMacAddr == null)
changeSelection(addr)
2020-02-13 09:25:39 -08:00
}
}
BTLog.debug("starting scan")
// filter and only accept devices that have a sw update service
val filter =
ScanFilter.Builder()
.setServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID))
.build()
val settings =
2020-02-13 19:02:40 -08:00
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
2020-02-13 09:25:39 -08:00
scanner.startScan(listOf(filter), settings, scanCallback)
onDispose {
BTLog.debug("stopping scan")
scanner.stopScan(scanCallback)
}
}
}
2020-02-13 19:02:40 -08:00
2020-02-13 09:25:39 -08:00
Column {
2020-02-13 19:02:40 -08:00
if (ScanState.errorText != null) {
Text("An unexpected error was encountered. Please file a bug on our github: ${ScanState.errorText}")
} else {
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
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 {
// 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
)
//}
}
}
}
}
CircularProgressIndicator() // Show that we are searching still
}
2020-02-13 09:25:39 -08:00
}
}
@Preview
@Composable
fun btScanScreenPreview() {
BTScanScreen()
}