feat: show progress indicator for device ops

This commit is contained in:
Fabian Christoffel
2023-06-26 18:17:29 +02:00
parent ec535b0ad2
commit e8a43711f4
5 changed files with 63 additions and 17 deletions

View File

@@ -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)
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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"