Skip to content

Commit

Permalink
Refactoring and adding possibility to observe Adapter state
Browse files Browse the repository at this point in the history
  • Loading branch information
weliem committed Jan 29, 2022
1 parent 38fe652 commit eeb6bc3
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.welie.blessedexample

import android.bluetooth.BluetoothAdapter
import android.content.Context
import com.welie.blessed.*
import kotlinx.coroutines.*
Expand All @@ -22,7 +23,7 @@ internal class BluetoothHandler private constructor(context: Context) {
val weightChannel = Channel<WeightMeasurement>(UNLIMITED)

private fun handlePeripheral(peripheral: BluetoothPeripheral) {
scope.launch(Dispatchers.IO) {
scope.launch {
try {
val mtu = peripheral.requestMtu(185)
Timber.i("MTU is $mtu")
Expand Down Expand Up @@ -307,6 +308,12 @@ internal class BluetoothHandler private constructor(context: Context) {
}
}

central.observeAdapterState { state ->
when(state) {
BluetoothAdapter.STATE_ON -> startScanning()
}
}

startScanning()
}
}
8 changes: 4 additions & 4 deletions app/src/main/java/com/welie/blessedexample/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class MainActivity : AppCompatActivity() {
if (missingPermissions.isNotEmpty()) {
requestPermissions(missingPermissions, ACCESS_LOCATION_REQUEST)
} else {
permissionsGranted()
checkIfLocationIsNeeded()
}
}

Expand All @@ -250,10 +250,10 @@ class MainActivity : AppCompatActivity() {
} else arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}

private fun permissionsGranted() {
// Check if Location services are on because they are required to make scanning work for SDK < 31
private fun checkIfLocationIsNeeded() {
val targetSdkVersion = applicationInfo.targetSdkVersion
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && targetSdkVersion < Build.VERSION_CODES.S) {
// Check if Location services are on because they are required to make scanning work for SDK < 31
if (checkLocationServices()) {
initBluetoothHandler()
}
Expand Down Expand Up @@ -313,7 +313,7 @@ class MainActivity : AppCompatActivity() {
}
}
if (allGranted) {
permissionsGranted()
checkIfLocationIsNeeded()
} else {
AlertDialog.Builder(this@MainActivity)
.setTitle("Location permission is required for scanning Bluetooth peripherals")
Expand Down
2 changes: 0 additions & 2 deletions blessed/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ android {
defaultConfig {
minSdkVersion 26
targetSdkVersion 31
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
59 changes: 41 additions & 18 deletions blessed/src/main/java/com/welie/blessed/BluetoothCentralManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package com.welie.blessed

import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
Expand Down Expand Up @@ -50,6 +51,8 @@ import kotlin.coroutines.suspendCoroutine
/**
* Central Manager class to scan and connect with bluetooth peripherals.
*/
@SuppressLint("MissingPermission")
@Suppress("unused")
class BluetoothCentralManager(private val context: Context) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val bluetoothAdapter: BluetoothAdapter
Expand All @@ -74,6 +77,7 @@ class BluetoothCentralManager(private val context: Context) {
private var disconnectRunnable: Runnable? = null
private val pinCodes: MutableMap<String, String> = ConcurrentHashMap()
private var currentResultCallback : ((BluetoothPeripheral, ScanResult) -> Unit)? = null
private var adapterStateCallback: (state: Int) -> Unit = {}

private val scanByNameCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
Expand Down Expand Up @@ -254,7 +258,7 @@ class BluetoothCentralManager(private val context: Context) {
setScanTimer()
currentCallback = scanCallback
currentFilters = filters
bluetoothScanner!!.startScan(filters, scanSettings, scanCallback)
bluetoothScanner?.startScan(filters, scanSettings, scanCallback)
Logger.i(TAG, "scan started")
} else {
Logger.e(TAG, "starting scan failed")
Expand Down Expand Up @@ -590,11 +594,11 @@ class BluetoothCentralManager(private val context: Context) {
throw IllegalArgumentException(message)
}
return if (connectedPeripherals.containsKey(peripheralAddress)) {
Objects.requireNonNull(connectedPeripherals[peripheralAddress])!!
requireNotNull(connectedPeripherals[peripheralAddress])
} else if (unconnectedPeripherals.containsKey(peripheralAddress)) {
Objects.requireNonNull(unconnectedPeripherals[peripheralAddress])!!
requireNotNull(unconnectedPeripherals[peripheralAddress])
} else if (scannedPeripherals.containsKey(peripheralAddress)) {
Objects.requireNonNull(scannedPeripherals[peripheralAddress])!!
requireNotNull(scannedPeripherals[peripheralAddress])
} else {
val peripheral = BluetoothPeripheral(context, bluetoothAdapter.getRemoteDevice(peripheralAddress), internalCallback)
scannedPeripherals[peripheralAddress] = peripheral
Expand Down Expand Up @@ -857,14 +861,17 @@ class BluetoothCentralManager(private val context: Context) {
}
}

@JvmField
val adapterStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
fun observeAdapterState(callback: (state: Int) -> Unit) {
this.adapterStateCallback = callback
}

private val adapterStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
// scope.launch { bluetoothCentralManagerCallback.onBluetoothAdapterStateChanged(state) }
handleAdapterState(state)
adapterStateCallback.invoke(state)
}
}
}
Expand All @@ -873,14 +880,32 @@ class BluetoothCentralManager(private val context: Context) {
when (state) {
BluetoothAdapter.STATE_OFF -> {
// Check if there are any connected peripherals or connections in progress
if (connectedPeripherals.size > 0 || unconnectedPeripherals.size > 0) {
if (connectedPeripherals.isNotEmpty() || unconnectedPeripherals.isNotEmpty()) {
// See if they are automatically disconnect
expectingBluetoothOffDisconnects = true
startDisconnectionTimer()
}
Logger.d(TAG, "bluetooth turned off")
}
BluetoothAdapter.STATE_TURNING_OFF -> {
// Stop all scans so that we are back in a clean state
if (isScanning) {
// Note that we can't call stopScan if the adapter is off
// On some phones like the Nokia 8, the adapter will be already off at this point
// So add a try/catch to handle any exceptions
try {
stopScan()
} catch (ignored: java.lang.Exception) {
}
}

if (isAutoScanning) {
try {
stopAutoconnectScan()
} catch (ignored: java.lang.Exception) {
}
}

expectingBluetoothOffDisconnects = true

// Stop all scans so that we are back in a clean state
Expand All @@ -890,9 +915,15 @@ class BluetoothCentralManager(private val context: Context) {
currentCallback = null
currentFilters = null
autoConnectScanner = null
bluetoothScanner = null
Logger.d(TAG, "bluetooth turning off")
}
BluetoothAdapter.STATE_ON -> {
// On some phones like Nokia 8, this scanner may still have an older active scan from us
// This happens when bluetooth is toggled. So make sure it is gone.
bluetoothScanner = bluetoothAdapter.bluetoothLeScanner
bluetoothScanner?.stopScan(defaultScanCallback)

expectingBluetoothOffDisconnects = false
Logger.d(TAG, "bluetooth turned on")
}
Expand All @@ -903,11 +934,11 @@ class BluetoothCentralManager(private val context: Context) {
}
}

fun disableLogging(): Unit {
fun disableLogging() {
Logger.enabled = false
}

fun enableLogging(): Unit {
fun enableLogging() {
Logger.enabled = false
}

Expand All @@ -921,20 +952,12 @@ class BluetoothCentralManager(private val context: Context) {
private const val NO_VALID_PERIPHERAL_CALLBACK_SPECIFIED = "no valid peripheral callback specified"
}

/**
* Construct a new BluetoothCentralManager object
*
* @param context Android application environment.
* @param bluetoothCentralManagerCallback the callback to call for updates
* @param handler Handler to use for callbacks.
*/
init {
val manager = context.getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = manager.adapter
autoConnectScanSettings = getScanSettings(ScanMode.LOW_POWER)
scanSettings = getScanSettings(ScanMode.LOW_LATENCY)

// Register for broadcasts on BluetoothAdapter state change
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(adapterStateReceiver, filter)
}
Expand Down
Loading

0 comments on commit eeb6bc3

Please sign in to comment.