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 ( )
}