feat: show progress indicator for device ops
This commit is contained in:
@@ -108,6 +108,10 @@ open class BleListener(private val deviceAddress: String?) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun onQueueSizeChange(groupedOps: Map<String, List<BleOperationType>>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -263,6 +267,14 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
listeners.filter { n -> n.isRelevantMessage(address) }.forEach(notifier)
|
listeners.filter { n -> n.isRelevantMessage(address) }.forEach(notifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notifyListenersOfQueueChange(address: String) {
|
||||||
|
notifyListeners(address) { listener ->
|
||||||
|
listener.onQueueSizeChange(
|
||||||
|
operationQueue.groupBy { o -> o.device.address }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun enqueueOperation(operation: BleOperationType) {
|
private fun enqueueOperation(operation: BleOperationType) {
|
||||||
@@ -270,15 +282,17 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
stopScan()
|
stopScan()
|
||||||
}
|
}
|
||||||
operationQueue.add(operation)
|
operationQueue.add(operation)
|
||||||
|
notifyListenersOfQueueChange(operation.device.address)
|
||||||
if (pendingOperation == null) {
|
if (pendingOperation == null) {
|
||||||
doNextOperation()
|
doNextOperation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun signalEndOfOperation() {
|
private fun signalEndOfOperation(op: BleOperationType) {
|
||||||
Log.d("ConnectionManager", "End of $pendingOperation")
|
Log.d("ConnectionManager", "End of $op")
|
||||||
pendingOperation = null
|
pendingOperation = null
|
||||||
|
notifyListenersOfQueueChange(op.device.address)
|
||||||
if (operationQueue.isNotEmpty()) {
|
if (operationQueue.isNotEmpty()) {
|
||||||
doNextOperation()
|
doNextOperation()
|
||||||
}
|
}
|
||||||
@@ -318,7 +332,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
"ConnectionManager",
|
"ConnectionManager",
|
||||||
"Not connected to ${operation.device.address}! Aborting $operation operation."
|
"Not connected to ${operation.device.address}! Aborting $operation operation."
|
||||||
)
|
)
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +345,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
notifyListeners(gatt.device.address) {
|
notifyListeners(gatt.device.address) {
|
||||||
it.onDisconnect(gatt)
|
it.onDisconnect(gatt)
|
||||||
}
|
}
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MtuRequest -> with(operation) {
|
is MtuRequest -> with(operation) {
|
||||||
@@ -348,7 +362,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
gatt.readCharacteristic(characteristic)
|
gatt.readCharacteristic(characteristic)
|
||||||
} else {
|
} else {
|
||||||
Log.e("ConnectionManager", "Char $charId (${serviceId}) is not readable!")
|
Log.e("ConnectionManager", "Char $charId (${serviceId}) is not readable!")
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -357,7 +371,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
val characteristic = gatt.getService(serviceId)?.getCharacteristic(charId);
|
val characteristic = gatt.getService(serviceId)?.getCharacteristic(charId);
|
||||||
if (characteristic == null) {
|
if (characteristic == null) {
|
||||||
Log.e("ConnectionManager", "Char $charId (${serviceId}) not found!")
|
Log.e("ConnectionManager", "Char $charId (${serviceId}) not found!")
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val descriptor = characteristic.getDescriptor(CCC_DESCRIPTOR_UUID)
|
val descriptor = characteristic.getDescriptor(CCC_DESCRIPTOR_UUID)
|
||||||
@@ -367,7 +381,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
"ConnectionManager", "Char ${characteristic.uuid} (service: $serviceId)" +
|
"ConnectionManager", "Char ${characteristic.uuid} (service: $serviceId)" +
|
||||||
" doesn't support notifications/indications"
|
" doesn't support notifications/indications"
|
||||||
)
|
)
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +393,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
"ConnectionManager", "setCharacteristicNotification to true " +
|
"ConnectionManager", "setCharacteristicNotification to true " +
|
||||||
"failed for ${characteristic.uuid} (service: $serviceId)"
|
"failed for ${characteristic.uuid} (service: $serviceId)"
|
||||||
)
|
)
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload =
|
payload =
|
||||||
@@ -393,7 +407,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
"ConnectionManager", "setCharacteristicNotification to false " +
|
"ConnectionManager", "setCharacteristicNotification to false " +
|
||||||
"failed for ${characteristic.uuid} (service: $serviceId)"
|
"failed for ${characteristic.uuid} (service: $serviceId)"
|
||||||
)
|
)
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(operation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
payload = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
||||||
@@ -428,8 +442,9 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
notifyListeners(gatt.device.address) {
|
notifyListeners(gatt.device.address) {
|
||||||
it.onConnect(gatt)
|
it.onConnect(gatt)
|
||||||
}
|
}
|
||||||
if (pendingOperation is Connect) {
|
val operation = pendingOperation
|
||||||
signalEndOfOperation()
|
if (operation is Connect) {
|
||||||
|
signalEndOfOperation(operation)
|
||||||
}
|
}
|
||||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
Log.e(
|
Log.e(
|
||||||
@@ -468,8 +483,9 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingOperation is DiscoverServicesRequest) {
|
val operation = pendingOperation
|
||||||
signalEndOfOperation()
|
if (operation is DiscoverServicesRequest) {
|
||||||
|
signalEndOfOperation(operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,8 +495,9 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
"ATT MTU changed to $mtu, success: ${status == BluetoothGatt.GATT_SUCCESS}"
|
"ATT MTU changed to $mtu, success: ${status == BluetoothGatt.GATT_SUCCESS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (pendingOperation is MtuRequest) {
|
val operation = pendingOperation
|
||||||
signalEndOfOperation()
|
if (operation is MtuRequest) {
|
||||||
|
signalEndOfOperation(operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +539,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
|
|
||||||
val op = pendingOperation
|
val op = pendingOperation
|
||||||
if (op is ReadChar && op.charId == characteristic.uuid && op.serviceId == characteristic.service.uuid) {
|
if (op is ReadChar && op.charId == characteristic.uuid && op.serviceId == characteristic.service.uuid) {
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +563,7 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
|||||||
it.onUnsubscribe(gatt, descriptor)
|
it.onUnsubscribe(gatt, descriptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signalEndOfOperation()
|
signalEndOfOperation(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.View.INVISIBLE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
@@ -33,6 +36,8 @@ interface DeviceListEntry {
|
|||||||
|
|
||||||
val status: String?
|
val status: String?
|
||||||
|
|
||||||
|
var hasRunningOp: Boolean
|
||||||
|
|
||||||
fun getActions(): List<Action>
|
fun getActions(): List<Action>
|
||||||
|
|
||||||
fun getMeasurements(): List<Measurement>
|
fun getMeasurements(): List<Measurement>
|
||||||
@@ -59,6 +64,7 @@ class DeviceListAdapter(
|
|||||||
val statusView: TextView
|
val statusView: TextView
|
||||||
val measurementsListView: ListView
|
val measurementsListView: ListView
|
||||||
val deviceActions: Button
|
val deviceActions: Button
|
||||||
|
val deviceProgress: ProgressBar
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -68,11 +74,13 @@ class DeviceListAdapter(
|
|||||||
statusView = view.findViewById(R.id.device_status)
|
statusView = view.findViewById(R.id.device_status)
|
||||||
measurementsListView = view.findViewById(R.id.measurement_fields)
|
measurementsListView = view.findViewById(R.id.measurement_fields)
|
||||||
deviceActions = view.findViewById(R.id.device_actions)
|
deviceActions = view.findViewById(R.id.device_actions)
|
||||||
|
deviceProgress = view.findViewById(R.id.device_progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun bind(result: DeviceListEntry) {
|
fun bind(result: DeviceListEntry) {
|
||||||
deviceNameView.text = result.name ?: "<N/A>"
|
deviceNameView.text = result.name ?: "<N/A>"
|
||||||
|
deviceProgress.visibility = if (result.hasRunningOp) VISIBLE else INVISIBLE
|
||||||
macAddressView.text = result.address
|
macAddressView.text = result.address
|
||||||
signalStrengthView.text = "${result.rssi ?: "-"} dBm"
|
signalStrengthView.text = "${result.rssi ?: "-"} dBm"
|
||||||
var signalStrengthIcon = R.drawable.signal_strength_weak
|
var signalStrengthIcon = R.drawable.signal_strength_weak
|
||||||
|
|||||||
@@ -100,9 +100,11 @@ class KirbyDevice(
|
|||||||
private val connectionManager: ConnectionManager,
|
private val connectionManager: ConnectionManager,
|
||||||
private val bleDevice: BluetoothDevice,
|
private val bleDevice: BluetoothDevice,
|
||||||
initialRssi: Int,
|
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)
|
statuses.add(DeviceStatus.DISCOVERED)
|
||||||
onStateChange(this)
|
onStateChange(this)
|
||||||
@@ -146,6 +148,11 @@ class KirbyDevice(
|
|||||||
onStateChange(this)
|
onStateChange(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onQueueSizeChange(groupedOps: Map<String, List<BleOperationType>>) {
|
||||||
|
hasRunningOp = groupedOps.getOrDefault(bleDevice.address, emptyList()).isNotEmpty()
|
||||||
|
onStateChange(this)
|
||||||
|
}
|
||||||
|
|
||||||
override var rssi = initialRssi
|
override var rssi = initialRssi
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
|
|||||||
@@ -338,6 +338,8 @@ class DummyListEntry(override val address: String) : DeviceListEntry {
|
|||||||
override val status: String
|
override val status: String
|
||||||
get() = "statusA, statusB"
|
get() = "statusA, statusB"
|
||||||
|
|
||||||
|
override var hasRunningOp: Boolean = true
|
||||||
|
|
||||||
override fun getActions(): List<Action> {
|
override fun getActions(): List<Action> {
|
||||||
return listOf(object : Action {
|
return listOf(object : Action {
|
||||||
override fun getLabel(): String {
|
override fun getLabel(): String {
|
||||||
|
|||||||
@@ -45,6 +45,18 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Device Name" />
|
tools:text="Device Name" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/device_progress"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/device_actions"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:indeterminateTint="?attr/colorPrimary"
|
||||||
|
android:paddingEnd="10dp" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/device_actions"
|
android:id="@+id/device_actions"
|
||||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
|
|||||||
Reference in New Issue
Block a user