2020-02-13 09:25:39 -08:00
|
|
|
package com.geeksville.mesh.ui
|
|
|
|
|
|
2020-04-08 18:42:17 -07:00
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.view.LayoutInflater
|
|
|
|
|
import android.view.View
|
|
|
|
|
import android.view.ViewGroup
|
|
|
|
|
import androidx.fragment.app.activityViewModels
|
|
|
|
|
import com.geeksville.android.Logging
|
|
|
|
|
import com.geeksville.mesh.R
|
|
|
|
|
import com.geeksville.mesh.model.UIViewModel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|
|
|
|
|
|
|
|
|
private val model: UIViewModel by activityViewModels()
|
|
|
|
|
|
|
|
|
|
override fun onCreateView(
|
|
|
|
|
inflater: LayoutInflater, container: ViewGroup?,
|
|
|
|
|
savedInstanceState: Bundle?
|
|
|
|
|
): View? {
|
|
|
|
|
return inflater.inflate(R.layout.settings_fragment, container, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-13 19:02:40 -08:00
|
|
|
|
2020-04-08 09:53:04 -07:00
|
|
|
/*
|
2020-02-13 19:02:40 -08:00
|
|
|
@Model
|
2020-02-18 08:56:53 -08:00
|
|
|
object ScanUIState {
|
2020-02-13 19:02:40 -08:00
|
|
|
var selectedMacAddr: String? = null
|
|
|
|
|
var errorText: String? = null
|
2020-02-18 08:56:53 -08:00
|
|
|
|
|
|
|
|
val devices = modelMapOf<String, BTScanEntry>()
|
|
|
|
|
|
|
|
|
|
/// Change to a new macaddr selection, updating GUI and radio
|
2020-02-18 09:09:49 -08:00
|
|
|
fun changeSelection(context: Context, newAddr: String) {
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanState.info("Changing BT device to $newAddr")
|
|
|
|
|
selectedMacAddr = newAddr
|
|
|
|
|
RadioInterfaceService.setBondedDeviceAddress(context, newAddr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// FIXME, remove once compose has better lifecycle management
|
|
|
|
|
object ScanState : Logging {
|
|
|
|
|
var scanner: BluetoothLeScanner? = null
|
|
|
|
|
var callback: ScanCallback? = null // SUPER NASTY FIXME
|
|
|
|
|
|
|
|
|
|
fun stopScan() {
|
|
|
|
|
if (callback != null) {
|
|
|
|
|
debug("stopping scan")
|
2020-03-06 20:55:47 -08:00
|
|
|
try {
|
|
|
|
|
scanner!!.stopScan(callback)
|
2020-03-03 11:00:01 -08:00
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
warn("Ignoring error stopping scan, probably BT adapter was disabled suddenly: ${ex.message}")
|
2020-03-06 20:55:47 -08:00
|
|
|
}
|
2020-02-18 10:40:02 -08:00
|
|
|
callback = null
|
2020-02-25 09:28:47 -08:00
|
|
|
}
|
2020-02-18 08:56:53 -08:00
|
|
|
}
|
2020-02-13 19:02:40 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Model
|
2020-02-13 19:54:05 -08:00
|
|
|
data class BTScanEntry(val name: String, val macAddress: String, val bonded: Boolean) {
|
2020-02-18 08:56:53 -08:00
|
|
|
val isSelected get() = macAddress == ScanUIState.selectedMacAddr
|
2020-02-13 19:02:40 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-04-07 12:13:50 -07:00
|
|
|
class BTScanFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) :
|
|
|
|
|
ComposeFragment(screenName, id, content) {
|
|
|
|
|
|
|
|
|
|
override fun onStop() {
|
|
|
|
|
ScanState.stopScan()
|
|
|
|
|
super.onStop()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-13 09:25:39 -08:00
|
|
|
@Composable
|
|
|
|
|
fun BTScanScreen() {
|
2020-03-02 10:30:32 -08:00
|
|
|
val context = ContextAmbient.current
|
2020-02-13 09:25:39 -08:00
|
|
|
|
|
|
|
|
/// Note: may be null on platforms without a bluetooth driver (ie. the emulator)
|
|
|
|
|
val bluetoothAdapter =
|
2020-02-18 10:40:02 -08:00
|
|
|
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
|
2020-02-13 19:02:40 -08:00
|
|
|
|
2020-04-07 10:40:01 -07:00
|
|
|
// FIXME - remove onCommit now that we have a fragement to run in
|
|
|
|
|
onCommit() {
|
2020-03-03 20:07:19 -08:00
|
|
|
ScanState.debug("BTScan component active")
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanUIState.selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context)
|
2020-02-13 09:25:39 -08:00
|
|
|
|
2020-02-18 09:09:49 -08:00
|
|
|
val scanCallback = object : ScanCallback() {
|
|
|
|
|
override fun onScanFailed(errorCode: Int) {
|
|
|
|
|
val msg = "Unexpected bluetooth scan failure: $errorCode"
|
2020-02-18 10:40:02 -08:00
|
|
|
// error code2 seeems to be indicate hung bluetooth stack
|
2020-02-18 09:09:49 -08:00
|
|
|
ScanUIState.errorText = msg
|
|
|
|
|
ScanState.reportError(msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
|
|
|
|
|
val addr = result.device.address
|
|
|
|
|
// prevent logspam because weill get get lots of redundant scan results
|
2020-03-30 16:45:09 -07:00
|
|
|
val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED
|
|
|
|
|
val oldEntry = ScanUIState.devices[addr]
|
|
|
|
|
if (oldEntry == null || oldEntry.bonded != isBonded) {
|
2020-02-18 09:09:49 -08:00
|
|
|
val entry = BTScanEntry(
|
|
|
|
|
result.device.name,
|
|
|
|
|
addr,
|
2020-03-30 16:45:09 -07:00
|
|
|
isBonded
|
2020-02-18 09:09:49 -08:00
|
|
|
)
|
|
|
|
|
ScanState.debug("onScanResult ${entry}")
|
|
|
|
|
ScanUIState.devices[addr] = entry
|
|
|
|
|
|
|
|
|
|
// If nothing was selected, by default select the first thing we see
|
|
|
|
|
if (ScanUIState.selectedMacAddr == null && entry.bonded)
|
|
|
|
|
ScanUIState.changeSelection(context, addr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-13 19:02:40 -08:00
|
|
|
if (bluetoothAdapter == null) {
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanState.warn("No bluetooth adapter. Running under emulation?")
|
2020-02-13 19:02:40 -08:00
|
|
|
|
|
|
|
|
val testnodes = listOf(
|
2020-02-13 19:54:05 -08:00
|
|
|
BTScanEntry("Meshtastic_ab12", "xx", false),
|
|
|
|
|
BTScanEntry("Meshtastic_32ac", "xb", true)
|
2020-02-13 19:02:40 -08:00
|
|
|
)
|
|
|
|
|
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanUIState.devices.putAll(testnodes.map { it.macAddress to it })
|
2020-02-13 19:02:40 -08:00
|
|
|
|
|
|
|
|
// If nothing was selected, by default select the first thing we see
|
2020-02-18 08:56:53 -08:00
|
|
|
if (ScanUIState.selectedMacAddr == null)
|
2020-02-18 09:09:49 -08:00
|
|
|
ScanUIState.changeSelection(context, testnodes.first().macAddress)
|
2020-02-13 19:02:40 -08:00
|
|
|
} else {
|
2020-02-29 14:14:52 -08:00
|
|
|
/// The following call might return null if the user doesn't have bluetooth access permissions
|
|
|
|
|
val s: BluetoothLeScanner? = bluetoothAdapter.bluetoothLeScanner
|
|
|
|
|
|
2020-03-03 20:07:19 -08:00
|
|
|
if (s == null) {
|
|
|
|
|
ScanUIState.errorText =
|
|
|
|
|
"This application requires bluetooth access. Please grant access in android settings."
|
|
|
|
|
} else {
|
2020-02-29 14:14:52 -08:00
|
|
|
ScanState.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 =
|
|
|
|
|
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
|
|
|
|
s.startScan(listOf(filter), settings, scanCallback)
|
|
|
|
|
ScanState.scanner = s
|
|
|
|
|
ScanState.callback = scanCallback
|
|
|
|
|
}
|
2020-02-18 08:56:53 -08:00
|
|
|
}
|
2020-02-13 09:25:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Column {
|
2020-02-18 08:56:53 -08:00
|
|
|
if (ScanUIState.errorText != null) {
|
2020-04-05 18:00:59 -07:00
|
|
|
Text(text = ScanUIState.errorText!!)
|
2020-02-13 19:02:40 -08:00
|
|
|
} else {
|
2020-02-18 08:56:53 -08:00
|
|
|
if (ScanUIState.devices.isEmpty()) {
|
2020-03-03 20:07:19 -08:00
|
|
|
Text(
|
|
|
|
|
text = "Looking for Meshtastic devices... (zero found)",
|
|
|
|
|
modifier = LayoutGravity.Center
|
|
|
|
|
)
|
2020-02-13 19:02:40 -08:00
|
|
|
|
2020-02-13 19:54:05 -08:00
|
|
|
CircularProgressIndicator() // Show that we are searching still
|
|
|
|
|
} else {
|
|
|
|
|
// val allPaired = bluetoothAdapter?.bondedDevices.orEmpty().map { it.address }.toSet()
|
|
|
|
|
|
2020-02-13 19:02:40 -08:00
|
|
|
RadioGroup {
|
|
|
|
|
Column {
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanUIState.devices.values.forEach {
|
2020-02-13 19:02:40 -08:00
|
|
|
// disabled pending https://issuetracker.google.com/issues/149528535
|
2020-04-05 22:17:40 -07:00
|
|
|
ProvideEmphasis(emphasis = if (it.bonded) MaterialTheme.emphasisLevels.high else MaterialTheme.emphasisLevels.disabled) {
|
2020-02-13 19:54:05 -08:00
|
|
|
RadioGroupTextItem(
|
|
|
|
|
selected = (it.isSelected),
|
|
|
|
|
onSelect = {
|
|
|
|
|
// If the device is paired, let user select it, otherwise start the pairing flow
|
2020-02-18 09:09:49 -08:00
|
|
|
if (it.bonded) {
|
|
|
|
|
ScanUIState.changeSelection(context, it.macAddress)
|
|
|
|
|
} else {
|
2020-02-18 08:56:53 -08:00
|
|
|
ScanState.info("Starting bonding for $it")
|
2020-02-13 19:54:05 -08:00
|
|
|
|
2020-03-30 17:37:02 -07:00
|
|
|
// We need this receiver to get informed when the bond attempt finished
|
|
|
|
|
val bondChangedReceiver = object : BroadcastReceiver() {
|
|
|
|
|
|
|
|
|
|
override fun onReceive(
|
|
|
|
|
context: Context,
|
|
|
|
|
intent: Intent
|
|
|
|
|
) = exceptionReporter {
|
|
|
|
|
val state =
|
|
|
|
|
intent.getIntExtra(
|
|
|
|
|
BluetoothDevice.EXTRA_BOND_STATE,
|
|
|
|
|
-1
|
|
|
|
|
)
|
|
|
|
|
ScanState.debug("Received bond state changed $state")
|
|
|
|
|
context.unregisterReceiver(this)
|
|
|
|
|
if (state == BluetoothDevice.BOND_BONDED || state == BluetoothDevice.BOND_BONDING) {
|
|
|
|
|
ScanState.debug("Bonding completed, connecting service")
|
|
|
|
|
ScanUIState.changeSelection(
|
|
|
|
|
context,
|
|
|
|
|
it.macAddress
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val filter = IntentFilter()
|
|
|
|
|
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
|
|
|
|
|
context.registerReceiver(bondChangedReceiver, filter)
|
|
|
|
|
|
2020-02-13 19:54:05 -08:00
|
|
|
// We ignore missing BT adapters, because it lets us run on the emulator
|
2020-02-18 08:56:53 -08:00
|
|
|
bluetoothAdapter
|
|
|
|
|
?.getRemoteDevice(it.macAddress)
|
2020-02-13 19:54:05 -08:00
|
|
|
?.createBond()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
text = it.name
|
|
|
|
|
)
|
|
|
|
|
}
|
2020-02-13 19:02:40 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-13 09:25:39 -08:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-08 09:53:04 -07:00
|
|
|
*/
|