feat: sensors can be bonded

Known issue: app enters an unrecoverable state if a pairing PIN with less than 6 digits is entered.
This commit is contained in:
Fabian Christoffel
2023-07-04 14:02:47 +02:00
parent 7ad1b4ea86
commit d6ab7faf30
9 changed files with 346 additions and 48 deletions

View File

@@ -3,16 +3,21 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- Request legacy Bluetooth permissions on versions older than API 31 (Android 12). --> <!-- Request legacy Bluetooth permissions on versions older than API 31 (Android 12). -->
<uses-permission android:name="android.permission.BLUETOOTH" <uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" <uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" <uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" <uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" <uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" /> tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

View File

@@ -3,15 +3,23 @@ package com.example.sensortestingapp
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.bluetooth.BluetoothDevice.BOND_BONDING
import android.bluetooth.BluetoothDevice.BOND_NONE
import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings import android.bluetooth.le.ScanSettings
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log import android.util.Log
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -62,6 +70,18 @@ data class DiscoverServicesRequest(
override val device: BluetoothDevice, override val device: BluetoothDevice,
) : BleOperationType() ) : BleOperationType()
data class BondRequest(
override val device: BluetoothDevice,
) : BleOperationType()
data class UnbondRequest(
override val device: BluetoothDevice,
) : BleOperationType()
data class ReadRemoteRssi(
override val device: BluetoothDevice,
) : BleOperationType()
open class BleListener(private val deviceAddress: String?) { open class BleListener(private val deviceAddress: String?) {
@@ -74,6 +94,8 @@ open class BleListener(private val deviceAddress: String?) {
open fun onConnect(gatt: BluetoothGatt) {} open fun onConnect(gatt: BluetoothGatt) {}
open fun onConnectToBondedFailed(gatt: BluetoothGatt) {}
open fun onDisconnect(gatt: BluetoothGatt) {} open fun onDisconnect(gatt: BluetoothGatt) {}
open fun onSuccessfulCharRead( open fun onSuccessfulCharRead(
@@ -112,6 +134,18 @@ open class BleListener(private val deviceAddress: String?) {
} }
open fun onBonded(device: BluetoothDevice) {
}
open fun onUnbonded(device: BluetoothDevice) {
}
open fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int) {
}
} }
@@ -179,6 +213,69 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
bleAdapter.bluetoothLeScanner bleAdapter.bluetoothLeScanner
} }
init {
val broadcastReceiver = object : BroadcastReceiver() {
private fun successfulBondingAttempt(prev: Int, curr: Int): Boolean {
return prev == BOND_BONDING && curr == BOND_BONDED || prev == BOND_NONE && curr == BOND_BONDED
}
private fun unsuccessfulBondingAttempt(prev: Int, curr: Int): Boolean {
return prev == BOND_BONDING && curr == BOND_NONE
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)!!
val previousBondState =
intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
val bondTransition = "${previousBondState.toBondStateDescription()} to " +
bondState.toBondStateDescription()
Log.i(
"ConnectionManager",
"${device.address} bond state changed | $bondTransition"
)
if (bondState == BOND_BONDED) {
notifyListeners(device.address) { it.onBonded(device) }
}
if (bondState == BOND_NONE) {
notifyListeners(device.address) { it.onUnbonded(device) }
}
val operation = pendingOperation
if (
(operation is BondRequest && (successfulBondingAttempt(
previousBondState, bondState
) || unsuccessfulBondingAttempt(
previousBondState, bondState
)) || operation is UnbondRequest && bondState == BOND_NONE)
&& operation.device.address == device.address
) {
signalEndOfOperation(operation)
}
}
}
private fun Int.toBondStateDescription() = when (this) {
BOND_BONDED -> "BONDED"
BOND_BONDING -> "BONDING"
BOND_NONE -> "NOT BONDED"
else -> "ERROR: $this"
}
}
context.registerReceiver(
broadcastReceiver,
IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
)
}
var isScanning = false var isScanning = false
set(value) { set(value) {
field = value field = value
@@ -260,6 +357,18 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
enqueueOperation(SetNotification(device, service, char, false)) enqueueOperation(SetNotification(device, service, char, false))
} }
fun bond(device: BluetoothDevice) {
enqueueOperation(BondRequest(device))
}
fun unbond(device: BluetoothDevice) {
enqueueOperation(UnbondRequest(device))
}
fun readRemoteRssi(device: BluetoothDevice) {
enqueueOperation(ReadRemoteRssi(device))
}
// - Beginning of PRIVATE functions // - Beginning of PRIVATE functions
@@ -325,10 +434,52 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
return return
} }
if (operation is BondRequest) {
if (operation.device.bondState == BOND_NONE) {
if (!operation.device.createBond()) {
Log.e(
"ConnectionManager", "createBond() returned false " +
"for device ${operation.device.address}"
)
signalEndOfOperation(operation)
}
} else {
Log.e(
"ConnectionManager",
"BondRequest for device ${operation.device.address} aborted. " +
"operation.device.bondState (${operation.device.bondState}) == BOND_NONE (${BOND_NONE})"
)
signalEndOfOperation(operation)
}
return
}
if (operation is UnbondRequest) {
if (operation.device.bondState == BOND_BONDED) {
val method = operation.device.javaClass.getMethod("removeBond")
val removeBondSuccessful = method.invoke(operation.device) as Boolean
if (!removeBondSuccessful) {
Log.e(
"ConnectionManager", "removeBond() returned false " +
"for device ${operation.device.address}"
)
signalEndOfOperation(operation)
}
} else {
Log.e(
"ConnectionManager",
"UnbondRequest for device ${operation.device.address} aborted. " +
"operation.device.bondState (${operation.device.bondState}) == BOND_BONDED (${BOND_BONDED})"
)
signalEndOfOperation(operation)
}
return
}
// Check BluetoothGatt availability for other operations // Check BluetoothGatt availability for other operations
val gatt = deviceGattMap[operation.device] val gatt = deviceGattMap[operation.device]
?: this@ConnectionManager.run { ?: this@ConnectionManager.run {
Log.e( Log.w(
"ConnectionManager", "ConnectionManager",
"Not connected to ${operation.device.address}! Aborting $operation operation." "Not connected to ${operation.device.address}! Aborting $operation operation."
) )
@@ -419,11 +570,15 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
} }
} }
is ReadRemoteRssi -> {
gatt.readRemoteRssi()
}
// necessary because of IDE type inference bug // necessary because of IDE type inference bug
// https://youtrack.jetbrains.com/issue/KTIJ-20749/Exhaustive-when-check-does-not-take-into-account-the-values-excluded-by-previous-if-conditions // https://youtrack.jetbrains.com/issue/KTIJ-20749/Exhaustive-when-check-does-not-take-into-account-the-values-excluded-by-previous-if-conditions
is Connect -> { is Connect, is BondRequest, is UnbondRequest -> {
Log.e("ConnectionManager", "Shouldn't get here") Log.e("ConnectionManager", "Shouldn't get here: $operation")
} }
} }
} }
@@ -431,8 +586,24 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
private val callback = object : BluetoothGattCallback() { private val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address val deviceAddress = gatt.device.address
val operation = pendingOperation
if (status == BluetoothGatt.GATT_SUCCESS) { if (operation is Connect
&& operation.device.bondState == BOND_BONDED
&& deviceAddress == operation.device.address
&& status == 133
&& newState == STATE_DISCONNECTED
) {
Log.i(
"ConnectionManager",
"onConnectionStateChange: Timeout when connecting to bonded device $deviceAddress. " +
"Device probably not in vicinity."
)
notifyListeners(gatt.device.address) {
it.onConnectToBondedFailed(gatt)
}
signalEndOfOperation(operation)
} else if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i( Log.i(
"ConnectionManager", "ConnectionManager",
@@ -442,11 +613,11 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
notifyListeners(gatt.device.address) { notifyListeners(gatt.device.address) {
it.onConnect(gatt) it.onConnect(gatt)
} }
val operation = pendingOperation
if (operation is Connect) { if (operation is Connect) {
signalEndOfOperation(operation) signalEndOfOperation(operation)
} }
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { } else if (newState == STATE_DISCONNECTED) {
Log.e( Log.e(
"ConnectionManager", "ConnectionManager",
"onConnectionStateChange: disconnected from $deviceAddress" "onConnectionStateChange: disconnected from $deviceAddress"
@@ -579,6 +750,20 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
} }
} }
override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) {
if (status == GATT_SUCCESS) {
notifyListeners(gatt.device.address) { it.onReadRemoteRssi(gatt, rssi) }
} else {
Log.e(
"BluetoothGattCallback",
"ReadRemoteRssi failed for ${gatt.device.address}, error: $status"
)
}
val op = pendingOperation
if (op is ReadRemoteRssi && gatt.device.address == op.device.address) {
signalEndOfOperation(op)
}
}
} }

View File

@@ -31,7 +31,7 @@ interface Measurement {
interface DeviceListEntry { interface DeviceListEntry {
val address: String val address: String
var rssi: Int var rssi: Int?
val name: String? val name: String?
@@ -80,14 +80,17 @@ class DeviceListAdapter(
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
fun bind(result: DeviceListEntry) { fun bind(result: DeviceListEntry) {
val rssi = result.rssi
deviceNameView.text = result.name ?: "<N/A>" deviceNameView.text = result.name ?: "<N/A>"
deviceProgress.visibility = if (result.hasRunningOp) VISIBLE else INVISIBLE deviceProgress.visibility = if (result.hasRunningOp) VISIBLE else INVISIBLE
macAddressView.text = result.address macAddressView.text = result.address
signalStrengthView.text = "${result.rssi ?: "-"} dBm" signalStrengthView.text = "${rssi ?: "-"} dBm"
var signalStrengthIcon = R.drawable.signal_strength_weak var signalStrengthIcon = R.drawable.signal_strength_weak
if (result.rssi >= -55) { if (rssi == null) {
signalStrengthIcon = R.drawable.signal_strength_none
} else if (rssi >= -55) {
signalStrengthIcon = R.drawable.signal_strength_strong signalStrengthIcon = R.drawable.signal_strength_strong
} else if (result.rssi >= -80) { } else if (rssi >= -80) {
signalStrengthIcon = R.drawable.signal_strength_medium signalStrengthIcon = R.drawable.signal_strength_medium
} }
signalStrengthView.setCompoundDrawablesWithIntrinsicBounds( signalStrengthView.setCompoundDrawablesWithIntrinsicBounds(

View File

@@ -2,6 +2,7 @@ package com.example.sensortestingapp
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor import android.bluetooth.BluetoothGattDescriptor
@@ -15,6 +16,7 @@ import java.util.EnumSet
import java.util.UUID import java.util.UUID
import java.util.stream.Collectors import java.util.stream.Collectors
private val DEMO_SERVICE_UUID = UUID.fromString("00000000-0001-11E1-9AB4-0002A5D5C51B") private val DEMO_SERVICE_UUID = UUID.fromString("00000000-0001-11E1-9AB4-0002A5D5C51B")
private val DEMO_CHAR_UUID = UUID.fromString("00140000-0001-11E1-AC36-0002A5D5C51B") private val DEMO_CHAR_UUID = UUID.fromString("00140000-0001-11E1-AC36-0002A5D5C51B")
@@ -92,26 +94,30 @@ private fun demoPayloadToMeasurements(payload: DemoPayload): List<Measurement> {
} }
enum class DeviceStatus { enum class DeviceStatus {
DISCOVERED, CONNECTED, BONDED, SUBSCRIBED CONNECTED, BONDED, SUBSCRIBED, MISSING
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
class KirbyDevice( class KirbyDevice(
private val connectionManager: ConnectionManager, private val connectionManager: ConnectionManager,
private val bleDevice: BluetoothDevice, private val bleDevice: BluetoothDevice,
initialRssi: Int,
override var hasRunningOp: Boolean = false,
private val onStateChange: (device: KirbyDevice) -> Unit private val onStateChange: (device: KirbyDevice) -> Unit
) : BleListener(bleDevice.address), DeviceListEntry { ) : BleListener(bleDevice.address), DeviceListEntry {
override fun onScanResult(callbackType: Int, result: ScanResult) { override fun onScanResult(callbackType: Int, result: ScanResult) {
statuses.add(DeviceStatus.DISCOVERED) rssi = result.rssi
onStateChange(this) onStateChange(this)
} }
override fun onConnect(gatt: BluetoothGatt) { override fun onConnect(gatt: BluetoothGatt) {
statuses.add(DeviceStatus.CONNECTED) statuses.add(DeviceStatus.CONNECTED)
statuses.remove(DeviceStatus.MISSING)
onStateChange(this)
}
override fun onConnectToBondedFailed(gatt: BluetoothGatt) {
statuses.add(DeviceStatus.MISSING)
onStateChange(this) onStateChange(this)
} }
@@ -131,7 +137,6 @@ class KirbyDevice(
override fun onCharChange(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { override fun onCharChange(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
addMeasurement(characteristic) addMeasurement(characteristic)
//statuses.add(DeviceStatus.SUBSCRIBED)
onStateChange(this) onStateChange(this)
} }
@@ -153,11 +158,25 @@ class KirbyDevice(
onStateChange(this) onStateChange(this)
} }
override var rssi = initialRssi override fun onBonded(device: BluetoothDevice) {
set(value) { statuses.add(DeviceStatus.BONDED)
field = value onStateChange(this)
onStateChange(this) }
}
override fun onUnbonded(device: BluetoothDevice) {
statuses.remove(DeviceStatus.BONDED)
onStateChange(this)
}
override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int) {
this.rssi = rssi
onStateChange(this)
}
override var hasRunningOp: Boolean = false
override var rssi: Int? = null
private fun addMeasurement(characteristic: BluetoothGattCharacteristic) { private fun addMeasurement(characteristic: BluetoothGattCharacteristic) {
if (characteristic.service.uuid == DEMO_SERVICE_UUID && characteristic.uuid == DEMO_CHAR_UUID) { if (characteristic.service.uuid == DEMO_SERVICE_UUID && characteristic.uuid == DEMO_CHAR_UUID) {
@@ -171,6 +190,12 @@ class KirbyDevice(
private val statuses = EnumSet.noneOf(DeviceStatus::class.java) private val statuses = EnumSet.noneOf(DeviceStatus::class.java)
init {
if (bleDevice.bondState == BOND_BONDED) {
statuses.add(DeviceStatus.BONDED)
}
}
override val address: String override val address: String
get() = bleDevice.address get() = bleDevice.address
@@ -181,10 +206,6 @@ class KirbyDevice(
override val status: String? override val status: String?
get() = statuses.stream().map { it.name }.collect(Collectors.joining(", ")) get() = statuses.stream().map { it.name }.collect(Collectors.joining(", "))
// override fun getRssi(): Int? {
// return rssi
// }
override fun getMeasurements(): List<Measurement> { override fun getMeasurements(): List<Measurement> {
if (measurements.isEmpty()) { if (measurements.isEmpty()) {
return emptyList() return emptyList()
@@ -195,7 +216,23 @@ class KirbyDevice(
override fun getActions(): List<Action> { override fun getActions(): List<Action> {
val actions = mutableListOf<Action>() val actions = mutableListOf<Action>()
if (statuses.contains(DeviceStatus.DISCOVERED) && !statuses.contains(DeviceStatus.CONNECTED) if (!statuses.contains(DeviceStatus.BONDED)) {
actions.add(object : Action {
override fun getLabel(): String {
return "Bond"
}
override fun getIcon(): Int {
return R.drawable.action_icon_bond
}
override fun execute() {
connectionManager.bond(bleDevice)
}
})
}
if (!statuses.contains(DeviceStatus.CONNECTED)
) { ) {
actions.add(object : Action { actions.add(object : Action {
override fun getLabel(): String { override fun getLabel(): String {
@@ -208,6 +245,7 @@ class KirbyDevice(
override fun execute() { override fun execute() {
connectionManager.connect(bleDevice) connectionManager.connect(bleDevice)
connectionManager.readRemoteRssi(bleDevice)
connectionManager.discoverServices(bleDevice) connectionManager.discoverServices(bleDevice)
} }
}) })
@@ -260,6 +298,21 @@ class KirbyDevice(
} }
}) })
} }
actions.add(object : Action {
override fun getLabel(): String {
return "Update Signal Strength"
}
override fun getIcon(): Int {
return R.drawable.action_icon_update_signal_strength
}
override fun execute() {
connectionManager.readRemoteRssi(bleDevice)
}
})
} }
if (statuses.contains(DeviceStatus.SUBSCRIBED)) { if (statuses.contains(DeviceStatus.SUBSCRIBED)) {
@@ -280,6 +333,22 @@ class KirbyDevice(
}) })
} }
if (statuses.contains(DeviceStatus.BONDED)
) {
actions.add(object : Action {
override fun getLabel(): String {
return "Unbond"
}
override fun getIcon(): Int {
return R.drawable.action_icon_unbond
}
override fun execute() {
connectionManager.unbond(bleDevice)
}
})
}
return actions; return actions;
} }

View File

@@ -5,6 +5,7 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanResult
import android.content.Context import android.content.Context
@@ -22,8 +23,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.example.sensortestingapp.databinding.ActivityMainBinding import com.example.sensortestingapp.databinding.ActivityMainBinding
@@ -91,21 +90,24 @@ class MainActivity : AppCompatActivity() {
} }
private fun isKirbyDevice(device: BluetoothDevice): Boolean {
return (device.name ?: "").lowercase().contains("kirby")
}
private fun setupDevicesList() { private fun setupDevicesList() {
binding.devicesList.apply { binding.devicesList.apply {
adapter = deviceListAdapter adapter = deviceListAdapter
layoutManager = LinearLayoutManager(
this@MainActivity,
RecyclerView.VERTICAL,
false
)
//isNestedScrollingEnabled = false
} }
val animator = binding.devicesList.itemAnimator val animator = binding.devicesList.itemAnimator
if (animator is SimpleItemAnimator) { if (animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false animator.supportsChangeAnimations = false
} }
bluetoothAdapter.bondedDevices.filter { isKirbyDevice(it) }.forEach {
newKirbyDevice(it)
}
//addDummyDevices() //addDummyDevices()
} }
@@ -131,7 +133,7 @@ class MainActivity : AppCompatActivity() {
} }
override fun onScanResult(callbackType: Int, result: ScanResult) { override fun onScanResult(callbackType: Int, result: ScanResult) {
if ((result.device.name ?: "").lowercase().contains("kirby")) { if (isKirbyDevice(result.device)) {
Log.i( Log.i(
"ScanCallback", "ScanCallback",
@@ -139,9 +141,7 @@ class MainActivity : AppCompatActivity() {
) )
val kirbyDevice = kirbyDevices.find { it.address == result.device.address } val kirbyDevice = kirbyDevices.find { it.address == result.device.address }
if (kirbyDevice == null) { if (kirbyDevice == null) {
newKirbyDevice(callbackType, result) newKirbyDevice(result.device).onScanResult(callbackType, result)
} else {
kirbyDevice.rssi = result.rssi
} }
} }
@@ -150,8 +150,8 @@ class MainActivity : AppCompatActivity() {
return mngr return mngr
} }
private fun newKirbyDevice(callbackType: Int, scanResult: ScanResult): KirbyDevice { private fun newKirbyDevice(bleDevice: BluetoothDevice): KirbyDevice {
val device = KirbyDevice(connectionManager, scanResult.device, scanResult.rssi) { val device = KirbyDevice(connectionManager, bleDevice) {
val i = kirbyDevices.indexOfFirst { d -> d === it } val i = kirbyDevices.indexOfFirst { d -> d === it }
runOnUiThread { runOnUiThread {
deviceListAdapter.notifyItemChanged(i) deviceListAdapter.notifyItemChanged(i)
@@ -159,7 +159,6 @@ class MainActivity : AppCompatActivity() {
} }
connectionManager.register(device) connectionManager.register(device)
kirbyDevices.add(device) kirbyDevices.add(device)
device.onScanResult(callbackType, scanResult)
deviceListAdapter.notifyItemInserted(kirbyDevices.size - 1) deviceListAdapter.notifyItemInserted(kirbyDevices.size - 1)
return device return device
} }
@@ -169,8 +168,6 @@ class MainActivity : AppCompatActivity() {
requestRelevantRuntimePermissions() requestRelevantRuntimePermissions()
} else { } else {
connectionManager.startScan() connectionManager.startScan()
// kirbyDevices.clear()
//deviceListAdapter.notifyDataSetChanged()
} }
} }
@@ -332,7 +329,7 @@ class MainActivity : AppCompatActivity() {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
class DummyListEntry(override val address: String) : DeviceListEntry { class DummyListEntry(override val address: String) : DeviceListEntry {
override var rssi: Int = -30 override var rssi: Int? = -30
override val name: String = "Device 123" override val name: String = "Device 123"
override val status: String override val status: String

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M8,11h8v2L8,13zM20.1,12L22,12c0,-2.76 -2.24,-5 -5,-5h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1zM3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM19,12h-2v3h-3v2h3v3h2v-3h3v-2h-3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1 0,1.43 -0.98,2.63 -2.31,2.98l1.46,1.46C20.88,15.61 22,13.95 22,12c0,-2.76 -2.24,-5 -5,-5zM16,11h-2.19l2,2L16,13zM2,4.27l3.11,3.11C3.29,8.12 2,9.91 2,12c0,2.76 2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1 0,-1.59 1.21,-2.9 2.76,-3.07L8.73,11L8,11v2h2.73L13,15.27L13,17h1.73l4.01,4L20,19.74 3.27,3 2,4.27z"/>
</vector>

View File

@@ -0,0 +1,17 @@
<vector android:height="16dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="16dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@color/greyDarker"
android:pathData="M17,4h3v16h-3zM5,14h3v6L5,20zM11,9h3v11h-3z" />
<path
android:fillColor="@android:color/black"
android:pathData="M5,14h3v6H5V14z" />
<path
android:fillColor="@android:color/black"
android:pathData="M5,14h3v6H5V14zM11,9h3v11h-3V9z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector android:height="16dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="16dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@color/greyDarker"
android:pathData="M17,4h3v16h-3zM5,14h3v6L5,20zM11,9h3v11h-3z" />
</vector>