mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
if bluetooth was disabled when we started, reattempt device connect later
This commit is contained in:
parent
25740c4fe4
commit
34aa4cde05
7 changed files with 156 additions and 73 deletions
|
|
@ -167,6 +167,10 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
private val btStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
model.bluetoothEnabled.value = enabled
|
||||
}
|
||||
|
||||
private fun requestPermission() {
|
||||
debug("Checking permissions")
|
||||
|
||||
|
|
@ -298,6 +302,9 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
private val isInTestLab: Boolean by lazy {
|
||||
(application as GeeksvilleApplication).isInTestLab
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -305,24 +312,14 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
val prefs = UIViewModel.getPreferences(this)
|
||||
model.ownerName.value = prefs.getString("owner", "")!!
|
||||
|
||||
val isInTestLab = (application as GeeksvilleApplication).isInTestLab
|
||||
|
||||
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||
// displays a dialog requesting user permission to enable Bluetooth.
|
||||
if (bluetoothAdapter != null && !isInTestLab) {
|
||||
bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply {
|
||||
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.error_bluetooth,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
/// Set initial bluetooth state
|
||||
bluetoothAdapter?.apply {
|
||||
model.bluetoothEnabled.value = isEnabled
|
||||
}
|
||||
|
||||
/// We now want to be informed of bluetooth state
|
||||
registerReceiver(btStateReceiver, btStateReceiver.intent)
|
||||
|
||||
// if (!isInTestLab) - very important - even in test lab we must request permissions because we need location perms for some of our tests to pass
|
||||
requestPermission()
|
||||
|
||||
|
|
@ -399,6 +396,8 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
unregisterReceiver(btStateReceiver)
|
||||
unregisterMeshReceiver()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
@ -669,6 +668,15 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||
// displays a dialog requesting user permission to enable Bluetooth.
|
||||
if (!isInTestLab) {
|
||||
bluetoothAdapter?.takeIf { !it.isEnabled }?.apply {
|
||||
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
|
||||
}
|
||||
}
|
||||
|
||||
bindMeshService()
|
||||
|
||||
val bonded =
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@ class UIViewModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
}
|
||||
|
||||
|
||||
val bluetoothEnabled = object : MutableLiveData<Boolean>(false) {
|
||||
}
|
||||
|
||||
|
||||
/// If the app was launched because we received a new channel intent, the Url will be here
|
||||
var requestedChannelUrl: Uri? = null
|
||||
|
||||
|
|
|
|||
|
|
@ -395,13 +395,23 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user turns on bluetooth after we start, make sure to try and reconnected then
|
||||
*/
|
||||
private val bluetoothStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
if (enabled)
|
||||
setEnabled(true)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
runningService = this
|
||||
super.onCreate()
|
||||
setEnabled(true)
|
||||
registerReceiver(bluetoothStateReceiver, bluetoothStateReceiver.intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(bluetoothStateReceiver)
|
||||
setEnabled(false)
|
||||
serviceJob.cancel()
|
||||
runningService = null
|
||||
|
|
|
|||
|
|
@ -21,6 +21,24 @@ import java.util.*
|
|||
/// Return a standard BLE 128 bit UUID from the short 16 bit versions
|
||||
fun longBLEUUID(hexFour: String) = UUID.fromString("0000$hexFour-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
|
||||
/**
|
||||
* A helper class to call onChanged when bluetooth is enabled or disabled
|
||||
*/
|
||||
class BluetoothStateReceiver(val onChanged: (Boolean) -> Unit) : BroadcastReceiver() {
|
||||
val intent = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) // Can be used for registering
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||
if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
|
||||
// Simulate a disconnection if the user disables bluetooth entirely
|
||||
BluetoothAdapter.STATE_OFF -> onChanged(false)
|
||||
BluetoothAdapter.STATE_ON -> onChanged(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses coroutines to safely access a bluetooth GATT device with a synchronous API
|
||||
*
|
||||
|
|
@ -51,28 +69,19 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
private val notifyHandlers = mutableMapOf<UUID, (BluetoothGattCharacteristic) -> Unit>()
|
||||
|
||||
/// When we see the BT stack getting disabled/renabled we handle that as a connect/disconnect event
|
||||
private val btStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||
if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
val newstate = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
|
||||
when (newstate) {
|
||||
// Simulate a disconnection if the user disables bluetooth entirely
|
||||
BluetoothAdapter.STATE_OFF -> {
|
||||
if (state == BluetoothProfile.STATE_CONNECTED)
|
||||
gattCallback.onConnectionStateChange(
|
||||
gatt!!,
|
||||
0,
|
||||
BluetoothProfile.STATE_DISCONNECTED
|
||||
)
|
||||
else
|
||||
debug("We were not connected, so ignoring bluetooth shutdown")
|
||||
}
|
||||
BluetoothAdapter.STATE_ON -> {
|
||||
warn("requeue a connect anytime bluetooth is reenabled")
|
||||
reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
private val btStateReceiver = BluetoothStateReceiver { enabled ->
|
||||
if (!enabled) {
|
||||
if (state == BluetoothProfile.STATE_CONNECTED)
|
||||
gattCallback.onConnectionStateChange(
|
||||
gatt!!,
|
||||
0,
|
||||
BluetoothProfile.STATE_DISCONNECTED
|
||||
)
|
||||
else
|
||||
debug("We were not connected, so ignoring bluetooth shutdown")
|
||||
} else {
|
||||
warn("requeue a connect anytime bluetooth is reenabled")
|
||||
reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +92,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
init {
|
||||
context.registerReceiver(
|
||||
btStateReceiver,
|
||||
IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
btStateReceiver.intent
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
/**
|
||||
* A fragment that represents a current 'screen' in our app.
|
||||
|
|
@ -9,9 +14,24 @@ import com.geeksville.android.GeeksvilleApplication
|
|||
* Useful for tracking analytics
|
||||
*/
|
||||
open class ScreenFragment(private val screenName: String) : Fragment() {
|
||||
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val bluetoothManager =
|
||||
requireContext().getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
bluetoothManager.adapter
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
GeeksvilleApplication.analytics.sendScreenView(screenName)
|
||||
|
||||
// Keep reminding user BLE is still off
|
||||
if (bluetoothAdapter?.isEnabled != true) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.error_bluetooth,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
|||
|
|
@ -158,11 +158,14 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
fun startScan() {
|
||||
/**
|
||||
* returns true if we could start scanning, false otherwise
|
||||
*/
|
||||
fun startScan(): Boolean {
|
||||
debug("BTScan component active")
|
||||
selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context)
|
||||
|
||||
if (bluetoothAdapter == null) {
|
||||
return if (bluetoothAdapter == null) {
|
||||
warn("No bluetooth adapter. Running under emulation?")
|
||||
|
||||
val testnodes = listOf(
|
||||
|
|
@ -178,6 +181,8 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
GeeksvilleApplication.currentActivity as MainActivity,
|
||||
testnodes.first().macAddress
|
||||
)
|
||||
|
||||
true
|
||||
} else {
|
||||
/// The following call might return null if the user doesn't have bluetooth access permissions
|
||||
val s: BluetoothLeScanner? = bluetoothAdapter.bluetoothLeScanner
|
||||
|
|
@ -185,20 +190,28 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
if (s == null) {
|
||||
errorText.value =
|
||||
context.getString(R.string.requires_bluetooth)
|
||||
|
||||
false
|
||||
} else {
|
||||
debug("starting scan")
|
||||
if (scanner == null) {
|
||||
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()
|
||||
// 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)
|
||||
scanner = s
|
||||
val settings =
|
||||
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
.build()
|
||||
s.startScan(listOf(filter), settings, scanCallback)
|
||||
scanner = s
|
||||
} else {
|
||||
debug("scan already running")
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -342,13 +355,25 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
/// Show the GUI for classic scanning
|
||||
private fun showClassicWidgets(visible: Int) {
|
||||
scanProgressBar.visibility = visible
|
||||
deviceRadioGroup.visibility = visible
|
||||
}
|
||||
|
||||
/// Setup the GUI to do a classic (pre SDK 26 BLE scan)
|
||||
private fun initClassicScan() {
|
||||
// Turn off the widgets for the new API
|
||||
scanProgressBar.visibility = View.VISIBLE
|
||||
deviceRadioGroup.visibility = View.VISIBLE
|
||||
// Turn off the widgets for the new API (we turn on/off hte classic widgets when we start scanning
|
||||
changeRadioButton.visibility = View.GONE
|
||||
|
||||
model.bluetoothEnabled.observe(viewLifecycleOwner, Observer { enabled ->
|
||||
showClassicWidgets(if (enabled) View.VISIBLE else View.GONE)
|
||||
if (enabled)
|
||||
scanModel.startScan()
|
||||
else
|
||||
scanModel.stopScan()
|
||||
})
|
||||
|
||||
scanModel.errorText.observe(viewLifecycleOwner, Observer { errMsg ->
|
||||
if (errMsg != null) {
|
||||
scanStatusText.text = errMsg
|
||||
|
|
@ -359,24 +384,29 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
// Remove the old radio buttons and repopulate
|
||||
deviceRadioGroup.removeAllViews()
|
||||
|
||||
var hasShownOurDevice = false
|
||||
devices.values.forEach { device ->
|
||||
hasShownOurDevice =
|
||||
hasShownOurDevice || device.macAddress == scanModel.selectedMacAddr
|
||||
addDeviceButton(device, true)
|
||||
}
|
||||
val adapter = scanModel.bluetoothAdapter!!
|
||||
if (adapter.isEnabled) {
|
||||
// This code requres BLE to be enabled
|
||||
|
||||
var hasShownOurDevice = false
|
||||
devices.values.forEach { device ->
|
||||
hasShownOurDevice =
|
||||
hasShownOurDevice || device.macAddress == scanModel.selectedMacAddr
|
||||
addDeviceButton(device, true)
|
||||
}
|
||||
|
||||
// The device the user is already paired with is offline currently, still show it
|
||||
// it in the list, but greyed out
|
||||
val selectedAddr = scanModel.selectedMacAddr
|
||||
if (!hasShownOurDevice && selectedAddr != null) {
|
||||
val bDevice = scanModel.bluetoothAdapter!!.getRemoteDevice(selectedAddr)
|
||||
val curDevice = BTScanModel.BTScanEntry(
|
||||
bDevice.name,
|
||||
bDevice.address,
|
||||
bDevice.bondState == BOND_BONDED
|
||||
)
|
||||
addDeviceButton(curDevice, false)
|
||||
// The device the user is already paired with is offline currently, still show it
|
||||
// it in the list, but greyed out
|
||||
val selectedAddr = scanModel.selectedMacAddr
|
||||
if (!hasShownOurDevice && selectedAddr != null) {
|
||||
val bDevice = scanModel.bluetoothAdapter!!.getRemoteDevice(selectedAddr)
|
||||
val curDevice = BTScanModel.BTScanEntry(
|
||||
bDevice.name,
|
||||
bDevice.address,
|
||||
bDevice.bondState == BOND_BONDED
|
||||
)
|
||||
addDeviceButton(curDevice, false)
|
||||
}
|
||||
}
|
||||
|
||||
val hasBonded =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue