WIP - doesn't yet build, but changing to set the device addresses the correct way

This commit is contained in:
geeksville
2020-04-19 19:23:20 -07:00
parent 513f56f88f
commit cde3f2dec2
6 changed files with 111 additions and 91 deletions

View File

@@ -57,8 +57,14 @@ interface IMeshService {
*/
String connectionState();
/// If a macaddress we will try to talk to our device, if null we will be idle.
/// Users should not call this directly, only used internally by the MeshUtil activity
void setDeviceAddress(String deviceAddr);
// see com.geeksville.com.geeksville.mesh broadcast intents
// RECEIVED_OPAQUE for data received from other nodes. payload will contain a DataPacket
// NODE_CHANGE for new IDs appearing or disappearing
// CONNECTION_CHANGED for losing/gaining connection to the packet radio
}

View File

@@ -22,6 +22,7 @@ interface IRadioInterfaceService {
byte []readOwner();
void writeOwner(in byte [] owner);
/// If true we will try to talk to our device, if false we will shutdown. Useful during software update.
void enableLink(boolean enable);
/// If a macaddress we will try to talk to our device, if null we will be idle.
/// Users should not call this directly, called only by MeshService
void setDeviceAddress(String deviceAddr);
}

View File

@@ -436,7 +436,9 @@ class MainActivity : AppCompatActivity(), Logging,
}
// ... Continue interacting with the paired device.
RadioInterfaceService.setBondedDeviceAddress(this, device.address)
model.meshService?.let { service ->
service.setDeviceAddress(device.address)
}
}
else ->
@@ -636,7 +638,7 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
fun bindMeshService() {
private fun bindMeshService() {
debug("Binding to mesh service!")
// we bind using the well known name, to make sure 3rd party apps could also
if (model.meshService != null)
@@ -648,7 +650,7 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
fun unbindMeshService() {
private fun unbindMeshService() {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
// if we never connected, do nothing

View File

@@ -37,7 +37,7 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
class RadioNotConnectedException() : Exception("Not connected to radio")
class RadioNotConnectedException(message: String = "Not connected to radio") : Exception(message)
private val errorHandler = CoroutineExceptionHandler { _, exception ->
@@ -73,35 +73,30 @@ class MeshService : Service(), Logging {
/// Helper function to start running our service, returns the intent used to reach it
/// or null if the service could not be started (no bluetooth or no bonded device set)
fun startService(context: Context): Intent? {
if (RadioInterfaceService.getBondedDeviceAddress(context) == null) {
warn("No mesh radio is bonded, not starting service")
return null
} else {
// bind to our service using the same mechanism an external client would use (for testing coverage)
// The following would work for us, but not external users
//val intent = Intent(this, MeshService::class.java)
//intent.action = IMeshService::class.java.name
val intent = Intent()
intent.setClassName(
"com.geeksville.mesh",
"com.geeksville.mesh.service.MeshService"
)
// bind to our service using the same mechanism an external client would use (for testing coverage)
// The following would work for us, but not external users
//val intent = Intent(this, MeshService::class.java)
//intent.action = IMeshService::class.java.name
val intent = Intent()
intent.setClassName(
"com.geeksville.mesh",
"com.geeksville.mesh.service.MeshService"
)
// Before binding we want to explicitly create - so the service stays alive forever (so it can keep
// listening for the bluetooth packets arriving from the radio. And when they arrive forward them
// to Signal or whatever.
// Before binding we want to explicitly create - so the service stays alive forever (so it can keep
// listening for the bluetooth packets arriving from the radio. And when they arrive forward them
// to Signal or whatever.
logAssert(
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// we have some samsung devices failing with https://issuetracker.google.com/issues/76112072#comment56 not sure what the fix is yet
context.startForegroundService(intent)
} else {
context.startService(intent)
}) != null
)
logAssert(
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// we have some samsung devices failing with https://issuetracker.google.com/issues/76112072#comment56 not sure what the fix is yet
context.startForegroundService(intent)
} else {
context.startService(intent)
}) != null
)
return intent
}
return intent
}
}
@@ -1069,6 +1064,12 @@ class MeshService : Service(), Logging {
}
private val binder = object : IMeshService.Stub() {
override fun setDeviceAddress(deviceAddr: String?) {
debug("Passing through device change to radio service: $deviceAddr")
connectedRadio.setDeviceAddress(deviceAddr)
}
// Note: bound methods don't get properly exception caught/logged, so do that with a wrapper
// per https://blog.classycode.com/dealing-with-exceptions-in-aidl-9ba904c6d63
override fun subscribeReceiver(packageName: String, receiverName: String) =

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Service
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.companion.CompanionDeviceManager
import android.content.Context
@@ -184,52 +185,6 @@ class RadioInterfaceService : Service(), Logging {
}
@SuppressLint("NewApi")
fun setBondedDeviceAddress(context: Context, addr: String?) {
// Record that this use has configured a radio
GeeksvilleApplication.analytics.track(
"mesh_bond"
)
debug("Setting bonded device to $addr")
if (hasCompanionDeviceApi((context))) {
// We only keep an association to one device at a time...
if (addr != null) {
val deviceManager = context.getSystemService(CompanionDeviceManager::class.java)
deviceManager.associations.forEach { old ->
if (addr != old) {
debug("Forgetting old BLE association $old")
deviceManager.disassociate(old)
}
}
}
} else {
getPrefs(context).edit(commit = true) {
if (addr == null)
this.remove(DEVADDR_KEY)
else
putString(DEVADDR_KEY, addr)
}
}
// Force the service to reconnect
val s = runningService
if (s != null) {
// Ignore any errors that happen while closing old device
ignoreException {
info("shutting down old service")
s.setEnabled(false) // nasty, needed to force the next setEnabled call to reconnect
}
info("Setting enable on the running radio service")
s.setEnabled(addr != null)
}
if (addr != null) {
info("We have a device addr now, starting mesh service")
MeshService.startService(context)
}
}
/// Can we use the modern BLE scan API?
fun hasCompanionDeviceApi(context: Context): Boolean = false /* ALAS - not ready for production yet
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -250,8 +205,11 @@ class RadioInterfaceService : Service(), Logging {
/// Our BLE device
val device get() = safe!!.gatt!!
/// Our service
val service get() = device.getService(BTM_SERVICE_UUID)
/// Our service - note - it is possible to get back a null response for getService if the device services haven't yet been found
val service
get(): BluetoothGattService = device.getService(BTM_SERVICE_UUID)
?: throw RadioNotConnectedException("BLE service not found")
//.services.find { it.uuid == BTM_SERVICE_UUID }!!
private lateinit var fromNum: BluetoothGattCharacteristic
@@ -328,6 +286,49 @@ class RadioInterfaceService : Service(), Logging {
}
@SuppressLint("NewApi")
fun setBondedDeviceAddress(addr: String?) {
// Record that this use has configured a radio
GeeksvilleApplication.analytics.track(
"mesh_bond"
)
// Ignore any errors that happen while closing old device
ignoreException {
Companion.info("shutting down old service")
setEnabled(false) // nasty, needed to force the next setEnabled call to reconnect
}
debug("Setting bonded device to $addr")
if (hasCompanionDeviceApi(this)) {
// We only keep an association to one device at a time...
if (addr != null) {
val deviceManager = getSystemService(CompanionDeviceManager::class.java)
deviceManager.associations.forEach { old ->
if (addr != old) {
Companion.debug("Forgetting old BLE association $old")
deviceManager.disassociate(old)
}
}
}
} else {
getPrefs(this).edit(commit = true) {
if (addr == null)
this.remove(DEVADDR_KEY)
else
putString(DEVADDR_KEY, addr)
}
}
// Force the service to reconnect
if (addr != null) {
info("Setting enable on the running radio service")
setEnabled(true)
}
}
private fun onDisconnect() {
broadcastConnectionChanged(false)
isConnected = false
@@ -418,7 +419,7 @@ class RadioInterfaceService : Service(), Logging {
} else {
val address = getBondedDeviceAddress(this)
if (address == null)
errormsg("No bonded mesh radio, can't create service")
errormsg("No bonded mesh radio, can't start service")
else {
// Note: this call does no comms, it just creates the device object (even if the
// device is off/not connected)
@@ -518,8 +519,9 @@ class RadioInterfaceService : Service(), Logging {
}
private val binder = object : IRadioInterfaceService.Stub() {
override fun enableLink(enable: Boolean) = toRemoteExceptions {
setEnabled(enable)
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
setBondedDeviceAddress(deviceAddr)
}
// A write of any size to nodeinfo means restart reading

View File

@@ -36,15 +36,18 @@ object SLogging : Logging {}
/// Change to a new macaddr selection, updating GUI and radio
fun changeDeviceSelection(context: MainActivity, newAddr: String?) {
RadioInterfaceService.setBondedDeviceAddress(context, newAddr)
model.meshService?.let { service ->
service.setDeviceAddress(context, newAddr)
// Super ugly hack. we force the activity to reconnect FIXME, find a cleaner way
context.unbindMeshService()
context.bindMeshService()
}
}
/// Show the UI asking the user to bond with a device, call changeSelection() if/when bonding completes
private fun requestBonding(activity: MainActivity, device: BluetoothDevice, onSuccess: () -> Unit) {
private fun requestBonding(
activity: MainActivity,
device: BluetoothDevice,
onSuccess: () -> Unit
) {
SLogging.info("Starting bonding for $device")
// We need this receiver to get informed when the bond attempt finished
@@ -132,7 +135,10 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
// If nothing was selected, by default select the first thing we see
if (selectedMacAddr == null && entry.bonded)
changeScanSelection(GeeksvilleApplication.currentActivity as MainActivity, addr)
changeScanSelection(
GeeksvilleApplication.currentActivity as MainActivity,
addr
)
devices.value = oldDevs + Pair(addr, entry) // trigger gui updates
}
@@ -347,14 +353,16 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (!device.bonded)
scanStatusText.setText(R.string.starting_pairing)
b.isSelected = scanModel.onSelected(requireActivity() as MainActivity, device)
b.isSelected =
scanModel.onSelected(requireActivity() as MainActivity, device)
if (!b.isSelected)
scanStatusText.setText(R.string.pairing_failed)
}
}
val hasBonded = RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null
val hasBonded =
RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null
// get rid of the warning text once at least one device is paired
warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE