diff --git a/CHANGELOG.md b/CHANGELOG.md index 05cb4ea8f7..de1de6536a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Attention: don't forget to add the flag for F-Droid before release - [FIX] Fix disappeared file manager - [FIX] Add deeplink fallback for flipper scheme uri - [FIX] Change description of remotes library card +- [FIX] Fix bytes race in new api - [CI] Fix merge-queue files diff - [CI] Add https://github.com/LionZXY/detekt-decompose-rule - [CI] Enabling detekt module for android and kmp modules diff --git a/components/bridge/connection/feature/actionnotifier/api/.gitignore b/components/bridge/connection/feature/actionnotifier/api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/components/bridge/connection/feature/actionnotifier/api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/components/bridge/connection/feature/actionnotifier/api/build.gradle.kts b/components/bridge/connection/feature/actionnotifier/api/build.gradle.kts new file mode 100644 index 0000000000..2dbc26d714 --- /dev/null +++ b/components/bridge/connection/feature/actionnotifier/api/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("flipper.multiplatform") + id("flipper.multiplatform-dependencies") +} + +android.namespace = "com.flipperdevices.bridge.connection.feature.actionnotifier.api" + +commonDependencies { + implementation(libs.kotlin.coroutines) +} diff --git a/components/bridge/connection/feature/actionnotifier/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/actionnotifier/api/FlipperActionNotifier.kt b/components/bridge/connection/feature/actionnotifier/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/actionnotifier/api/FlipperActionNotifier.kt new file mode 100644 index 0000000000..61f495e5cc --- /dev/null +++ b/components/bridge/connection/feature/actionnotifier/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/actionnotifier/api/FlipperActionNotifier.kt @@ -0,0 +1,14 @@ +package com.flipperdevices.bridge.connection.feature.actionnotifier.api + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +interface FlipperActionNotifier { + fun getActionFlow(): Flow + + fun notifyAboutAction() + + fun interface Factory { + fun invoke(scope: CoroutineScope): FlipperActionNotifier + } +} diff --git a/components/bridge/connection/feature/lagsdetector/impl/build.gradle.kts b/components/bridge/connection/feature/lagsdetector/impl/build.gradle.kts index 87ee4d6eb7..f7d1b70c9b 100644 --- a/components/bridge/connection/feature/lagsdetector/impl/build.gradle.kts +++ b/components/bridge/connection/feature/lagsdetector/impl/build.gradle.kts @@ -8,6 +8,7 @@ android.namespace = "com.flipperdevices.bridge.connection.feature.seriallagsdete commonDependencies { implementation(projects.components.bridge.connection.feature.lagsdetector.api) + implementation(projects.components.bridge.connection.feature.actionnotifier.api) implementation(projects.components.core.di) implementation(projects.components.core.log) diff --git a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureFactoryImpl.kt b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureFactoryImpl.kt index 81b1630e1b..bad0557e36 100644 --- a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureFactoryImpl.kt +++ b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureFactoryImpl.kt @@ -6,6 +6,7 @@ import com.flipperdevices.bridge.connection.feature.common.api.FDeviceFeatureQua import com.flipperdevices.bridge.connection.feature.common.api.FUnsafeDeviceFeatureApi import com.flipperdevices.bridge.connection.feature.restartrpc.api.FRestartRpcFeatureApi import com.flipperdevices.bridge.connection.transport.common.api.FConnectedDeviceApi +import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import com.flipperdevices.core.di.AppGraph import com.squareup.anvil.annotations.ContributesMultibinding import kotlinx.coroutines.CoroutineScope @@ -23,9 +24,13 @@ class FLagsDetectorFeatureFactoryImpl @Inject constructor( ): FDeviceFeatureApi? { val restartRpcFeature = unsafeFeatureDeviceApi.getUnsafe(FRestartRpcFeatureApi::class) ?: return null + val flipperActionNotifier = (connectedDevice as? FSerialDeviceApi) + ?.getActionNotifier() + ?: return null return lagsDetectorFeatureFactory( scope = scope, - restartRpcFeatureApi = restartRpcFeature + restartRpcFeatureApi = restartRpcFeature, + flipperActionNotifier = flipperActionNotifier ) } } diff --git a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureImpl.kt b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureImpl.kt index 1b7c00e443..172d29621f 100644 --- a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureImpl.kt +++ b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureImpl.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.feature.seriallagsdetector.impl +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.feature.restartrpc.api.FRestartRpcFeatureApi import com.flipperdevices.bridge.connection.feature.rpc.model.FlipperRequest import com.flipperdevices.bridge.connection.feature.seriallagsdetector.api.FLagsDetectorFeature @@ -20,13 +21,12 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch class FLagsDetectorFeatureImpl @AssistedInject constructor( - @Assisted scope: CoroutineScope, + @Assisted private val scope: CoroutineScope, @Assisted restartRpcFeatureApi: FRestartRpcFeatureApi, + @Assisted private val flipperActionNotifier: FlipperActionNotifier ) : FLagsDetectorFeature, LogTagProvider { override val TAG = "FlipperLagsDetector-${hashCode()}" - private val flipperActionNotifier = FlipperActionNotifierImpl(scope = scope) - private val pendingResponseCounter = PendingResponseCounter( onAction = flipperActionNotifier::notifyAboutAction ) @@ -49,7 +49,9 @@ class FLagsDetectorFeatureImpl @AssistedInject constructor( } } - override fun notifyAboutAction() = flipperActionNotifier.notifyAboutAction() + override fun notifyAboutAction() { + flipperActionNotifier.notifyAboutAction() + } override suspend fun wrapPendingAction( request: FlipperRequest?, @@ -79,6 +81,7 @@ class FLagsDetectorFeatureImpl @AssistedInject constructor( operator fun invoke( scope: CoroutineScope, restartRpcFeatureApi: FRestartRpcFeatureApi, + flipperActionNotifier: FlipperActionNotifier ): FLagsDetectorFeatureImpl } } diff --git a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FlipperActionNotifierImpl.kt b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FlipperActionNotifierImpl.kt index fcbd30e48e..e54594ca3e 100644 --- a/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FlipperActionNotifierImpl.kt +++ b/components/bridge/connection/feature/lagsdetector/impl/src/commonMain/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FlipperActionNotifierImpl.kt @@ -1,19 +1,25 @@ package com.flipperdevices.bridge.connection.feature.seriallagsdetector.impl +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier +import com.flipperdevices.core.di.AppGraph import com.flipperdevices.core.ktx.jre.FlipperDispatchers +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch +import me.gulya.anvil.assisted.ContributesAssistedFactory -class FlipperActionNotifierImpl( - private val scope: CoroutineScope -) { +@ContributesAssistedFactory(AppGraph::class, FlipperActionNotifier.Factory::class) +class FlipperActionNotifierImpl @AssistedInject constructor( + @Assisted private val scope: CoroutineScope +) : FlipperActionNotifier { private val actionFlow = MutableSharedFlow() - fun getActionFlow(): Flow = actionFlow + override fun getActionFlow(): Flow = actionFlow - fun notifyAboutAction() { + override fun notifyAboutAction() { scope.launch(FlipperDispatchers.workStealingDispatcher) { actionFlow.emit(Unit) } diff --git a/components/bridge/connection/feature/lagsdetector/impl/src/jvmSharedTest/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureTest.kt b/components/bridge/connection/feature/lagsdetector/impl/src/jvmSharedTest/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureTest.kt index f21d858b86..3fca479200 100644 --- a/components/bridge/connection/feature/lagsdetector/impl/src/jvmSharedTest/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureTest.kt +++ b/components/bridge/connection/feature/lagsdetector/impl/src/jvmSharedTest/kotlin/com/flipperdevices/bridge/connection/feature/seriallagsdetector/impl/FLagsDetectorFeatureTest.kt @@ -38,7 +38,8 @@ class FLagsDetectorFeatureTest { underTest = FLagsDetectorFeatureImpl( scope = childScope, - restartRpcFeatureApi = restartRpcFeatureApi + restartRpcFeatureApi = restartRpcFeatureApi, + flipperActionNotifier = FlipperActionNotifierImpl(childScope) ) val backgroundJob = backgroundScope.launch { underTest.wrapPendingAction(mockk(relaxed = true)) { @@ -65,7 +66,8 @@ class FLagsDetectorFeatureTest { underTest = FLagsDetectorFeatureImpl( scope = childScope, - restartRpcFeatureApi = restartRpcFeatureApi + restartRpcFeatureApi = restartRpcFeatureApi, + flipperActionNotifier = FlipperActionNotifierImpl(childScope) ) val backgroundJob = backgroundScope.launch { underTest.wrapPendingAction(mockk()) { diff --git a/components/bridge/connection/sample/build.gradle.kts b/components/bridge/connection/sample/build.gradle.kts index cb56d6725f..559106fa96 100644 --- a/components/bridge/connection/sample/build.gradle.kts +++ b/components/bridge/connection/sample/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(projects.components.bridge.connection.feature.getinfo.impl) implementation(projects.components.bridge.connection.feature.lagsdetector.api) implementation(projects.components.bridge.connection.feature.lagsdetector.impl) + implementation(projects.components.bridge.connection.feature.actionnotifier.api) implementation(projects.components.bridge.connection.feature.protocolversion.api) implementation(projects.components.bridge.connection.feature.protocolversion.impl) implementation(projects.components.bridge.connection.feature.provider.api) diff --git a/components/bridge/connection/transport/ble/impl/build.gradle.kts b/components/bridge/connection/transport/ble/impl/build.gradle.kts index 6eded413cb..e5635eb411 100644 --- a/components/bridge/connection/transport/ble/impl/build.gradle.kts +++ b/components/bridge/connection/transport/ble/impl/build.gradle.kts @@ -14,6 +14,7 @@ androidDependencies { implementation(projects.components.core.ktx) implementation(projects.components.bridge.connection.transport.common.api) + implementation(projects.components.bridge.connection.feature.actionnotifier.api) implementation(libs.ble.kotlin.scanner) implementation(libs.ble.kotlin.client) diff --git a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/api/FBleApiWithSerialFactory.kt b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/api/FBleApiWithSerialFactory.kt index a00133605a..d65cf7260d 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/api/FBleApiWithSerialFactory.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/api/FBleApiWithSerialFactory.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.transport.ble.impl.api +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.api.FBleDeviceSerialConfig import com.flipperdevices.bridge.connection.transport.ble.api.GATTCharacteristicAddress import com.flipperdevices.bridge.connection.transport.ble.impl.serial.FSerialDeviceApiWrapper @@ -13,7 +14,8 @@ import javax.inject.Inject class FBleApiWithSerialFactory @Inject constructor( private val serialDeviceApiWrapperFactory: FSerialDeviceApiWrapper.Factory, - private val fSerialRestartApiFactory: FSerialRestartApiImpl.Factory + private val fSerialRestartApiFactory: FSerialRestartApiImpl.Factory, + private val flipperActionNotifierFactory: FlipperActionNotifier.Factory ) { fun build( scope: CoroutineScope, @@ -22,7 +24,13 @@ class FBleApiWithSerialFactory @Inject constructor( metaInfoGattMap: ImmutableMap, statusListener: FTransportConnectionStatusListener ): FBleApiWithSerial { - val serialDeviceApi = serialDeviceApiWrapperFactory(scope, serialConfig, client.services) + val flipperActionNotifier = flipperActionNotifierFactory.invoke(scope) + val serialDeviceApi = serialDeviceApiWrapperFactory( + scope = scope, + config = serialConfig, + services = client.services, + flipperActionNotifier = flipperActionNotifier + ) val restartApi = fSerialRestartApiFactory( services = client.services, serialServiceUuid = serialConfig.serialServiceUuid, @@ -34,7 +42,7 @@ class FBleApiWithSerialFactory @Inject constructor( metaInfoGattMap = metaInfoGattMap, statusListener = statusListener, serialDeviceApi = serialDeviceApi, - serialRestartApi = restartApi + serialRestartApi = restartApi, ) } } diff --git a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialDeviceApiWrapper.kt b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialDeviceApiWrapper.kt index 268af7fe54..c3d74d6520 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialDeviceApiWrapper.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialDeviceApiWrapper.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.transport.ble.impl.serial +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.api.FBleDeviceSerialConfig import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import com.flipperdevices.core.ktx.jre.FlipperDispatchers @@ -22,6 +23,7 @@ class FSerialDeviceApiWrapper @AssistedInject constructor( @Assisted scope: CoroutineScope, @Assisted config: FBleDeviceSerialConfig, @Assisted serviceFlow: StateFlow, + @Assisted private val flipperActionNotifier: FlipperActionNotifier, serialApiFactory: SerialApiFactory ) : FSerialDeviceApi, LogTagProvider { override val TAG = "FSerialDeviceApiWrapper" @@ -29,6 +31,8 @@ class FSerialDeviceApiWrapper @AssistedInject constructor( private var delegateSerialApi: FSerialDeviceApi? = null private val lock = WaitNotifyLock() + override fun getActionNotifier(): FlipperActionNotifier = flipperActionNotifier + init { scope.launch { serviceFlow.collect { services -> @@ -46,7 +50,8 @@ class FSerialDeviceApiWrapper @AssistedInject constructor( val serialApi = serialApiFactory.build( config = config, services = services, - scope = newSerialApiScope + scope = newSerialApiScope, + flipperActionNotifier = flipperActionNotifier ) if (serialApi == null) { delegateSerialApi = null @@ -83,7 +88,8 @@ class FSerialDeviceApiWrapper @AssistedInject constructor( operator fun invoke( scope: CoroutineScope, config: FBleDeviceSerialConfig, - services: StateFlow + services: StateFlow, + flipperActionNotifier: FlipperActionNotifier ): FSerialDeviceApiWrapper } } diff --git a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialOverflowThrottler.kt b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialOverflowThrottler.kt index d5f397579a..ed7ebcbc11 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialOverflowThrottler.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialOverflowThrottler.kt @@ -3,6 +3,7 @@ package com.flipperdevices.bridge.connection.transport.ble.impl.serial import android.Manifest import android.content.Context import android.content.pm.PackageManager +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.impl.model.BLEConnectionPermissionException import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import com.flipperdevices.core.log.LogTagProvider @@ -32,6 +33,7 @@ class FSerialOverflowThrottler @AssistedInject constructor( @Assisted private val serialApi: FSerialDeviceApi, @Assisted private val scope: CoroutineScope, @Assisted private val overflowCharacteristic: ClientBleGattCharacteristic, + @Assisted private val flipperActionNotifier: FlipperActionNotifier, private val context: Context ) : FSerialDeviceApi, LogTagProvider { @@ -41,6 +43,8 @@ class FSerialOverflowThrottler @AssistedInject constructor( private var pendingBytes: ByteArray? = null + override fun getActionNotifier() = flipperActionNotifier + /** * Bytes waiting to be sent to the device */ @@ -149,8 +153,10 @@ class FSerialOverflowThrottler @AssistedInject constructor( remainBufferSize = 0 // here we end the while cycle } - bytesToSend = withTimeoutOrNull(SERIAL_SEND_WAIT_TIMEOUT_MS) { - channel.receive() + if (remainBufferSize > 0) { + bytesToSend = withTimeoutOrNull(SERIAL_SEND_WAIT_TIMEOUT_MS) { + channel.receive() + } } } @@ -175,7 +181,8 @@ class FSerialOverflowThrottler @AssistedInject constructor( operator fun invoke( serialApi: FSerialDeviceApi, scope: CoroutineScope, - overflowCharacteristic: ClientBleGattCharacteristic + overflowCharacteristic: ClientBleGattCharacteristic, + flipperActionNotifier: FlipperActionNotifier ): FSerialOverflowThrottler } } diff --git a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialUnsafeApiImpl.kt b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialUnsafeApiImpl.kt index 0419d7ba11..a21e782834 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialUnsafeApiImpl.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/FSerialUnsafeApiImpl.kt @@ -3,6 +3,7 @@ package com.flipperdevices.bridge.connection.transport.ble.impl.serial import android.Manifest import android.content.Context import android.content.pm.PackageManager +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.impl.model.BLEConnectionPermissionException import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import com.flipperdevices.bridge.connection.transport.common.api.serial.FlipperSerialSpeed @@ -31,7 +32,8 @@ class FSerialUnsafeApiImpl @AssistedInject constructor( @Assisted(DAGGER_ID_CHARACTERISTIC_RX) val rxCharacteristic: ClientBleGattCharacteristic, @Assisted(DAGGER_ID_CHARACTERISTIC_TX) val txCharacteristic: ClientBleGattCharacteristic, @Assisted scope: CoroutineScope, - private val context: Context + @Assisted private val flipperActionNotifier: FlipperActionNotifier, + private val context: Context, ) : FSerialDeviceApi, LogTagProvider { override val TAG = "FSerialUnsafeApiImpl" @@ -41,6 +43,8 @@ class FSerialUnsafeApiImpl @AssistedInject constructor( private val rxSpeed = SpeedMeter(scope) private val speedFlowState = MutableStateFlow(FlipperSerialSpeed()) + override fun getActionNotifier() = flipperActionNotifier + init { scope.launch { rxCharacteristic.getNotifications( @@ -54,6 +58,7 @@ class FSerialUnsafeApiImpl @AssistedInject constructor( rxSpeed.getSpeed(), txSpeed.getSpeed() ) { rxBPS, txBPS -> + flipperActionNotifier.notifyAboutAction() speedFlowState.emit( FlipperSerialSpeed(receiveBytesInSec = rxBPS, transmitBytesInSec = txBPS) ) @@ -80,7 +85,8 @@ class FSerialUnsafeApiImpl @AssistedInject constructor( operator fun invoke( @Assisted(DAGGER_ID_CHARACTERISTIC_RX) rxCharacteristic: ClientBleGattCharacteristic, @Assisted(DAGGER_ID_CHARACTERISTIC_TX) txCharacteristic: ClientBleGattCharacteristic, - scope: CoroutineScope + scope: CoroutineScope, + flipperActionNotifier: FlipperActionNotifier ): FSerialUnsafeApiImpl } } diff --git a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/SerialApiFactory.kt b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/SerialApiFactory.kt index 3a5acde312..0eb95d3401 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/SerialApiFactory.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidMain/kotlin/com/flipperdevices/bridge/connection/transport/ble/impl/serial/SerialApiFactory.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.transport.ble.impl.serial +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.api.FBleDeviceSerialConfig import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import com.flipperdevices.core.log.LogTagProvider @@ -11,13 +12,14 @@ import javax.inject.Inject class SerialApiFactory @Inject constructor( private val unsafeApiImplFactory: FSerialUnsafeApiImpl.Factory, - private val throttlerApiFactory: FSerialOverflowThrottler.Factory + private val throttlerApiFactory: FSerialOverflowThrottler.Factory, ) : LogTagProvider { override val TAG = "SerialApiCombinedFactory" fun build( config: FBleDeviceSerialConfig, services: ClientBleGattServices, - scope: CoroutineScope + scope: CoroutineScope, + flipperActionNotifier: FlipperActionNotifier ): FSerialDeviceApi? { val serialService = services.findService(config.serialServiceUuid) val rxCharacteristic = serialService?.findCharacteristic(config.rxServiceCharUuid) @@ -32,7 +34,8 @@ class SerialApiFactory @Inject constructor( var deviceApi: FSerialDeviceApi = unsafeApiImplFactory( rxCharacteristic = rxCharacteristic, txCharacteristic = txCharacteristic, - scope = scope + scope = scope, + flipperActionNotifier = flipperActionNotifier ) val overflowControlConfig = config.overflowControl @@ -47,7 +50,8 @@ class SerialApiFactory @Inject constructor( deviceApi = throttlerApiFactory( serialApi = deviceApi, scope = scope, - overflowCharacteristic = overflowCharacteristic + overflowCharacteristic = overflowCharacteristic, + flipperActionNotifier = flipperActionNotifier ) } diff --git a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialDeviceApiWrapperTest.kt b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialDeviceApiWrapperTest.kt index e5ba8a5960..cb270426f7 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialDeviceApiWrapperTest.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialDeviceApiWrapperTest.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.ble.impl.serial +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.api.FBleDeviceSerialConfig import com.flipperdevices.bridge.connection.transport.ble.impl.serial.FSerialDeviceApiWrapper import com.flipperdevices.bridge.connection.transport.ble.impl.serial.SerialApiFactory @@ -22,6 +23,7 @@ class FSerialDeviceApiWrapperTest { private lateinit var config: FBleDeviceSerialConfig private lateinit var serviceFlow: MutableStateFlow private lateinit var serialApiFactory: SerialApiFactory + private lateinit var flipperActionNotifier: FlipperActionNotifier @Before fun setUp() { @@ -32,9 +34,10 @@ class FSerialDeviceApiWrapperTest { overflowControl = null, resetCharUUID = UUID.fromString("00000000-0000-0000-0000-000000000004") ) + flipperActionNotifier = mockk(relaxed = true) serviceFlow = MutableStateFlow(null) serialApiFactory = mockk { - every { build(any(), any(), any()) } returns null + every { build(any(), any(), any(), any()) } returns null } } @@ -45,7 +48,8 @@ class FSerialDeviceApiWrapperTest { scope = childScope, config = config, serviceFlow = serviceFlow, - serialApiFactory = serialApiFactory + serialApiFactory = serialApiFactory, + flipperActionNotifier = flipperActionNotifier ) val services: ClientBleGattServices = mockk() val serialApi: FSerialDeviceApi = mockk(relaxUnitFun = true) @@ -54,7 +58,8 @@ class FSerialDeviceApiWrapperTest { serialApiFactory.build( config = eq(config), services = eq(services), - scope = any() + scope = any(), + flipperActionNotifier = any() ) } returns serialApi @@ -80,7 +85,8 @@ class FSerialDeviceApiWrapperTest { scope = childScope, config = config, serviceFlow = serviceFlow, - serialApiFactory = serialApiFactory + serialApiFactory = serialApiFactory, + flipperActionNotifier = flipperActionNotifier ) val services: ClientBleGattServices = mockk() val serialApi: FSerialDeviceApi = mockk(relaxUnitFun = true) @@ -89,7 +95,8 @@ class FSerialDeviceApiWrapperTest { serialApiFactory.build( config = eq(config), services = eq(services), - scope = any() + scope = any(), + flipperActionNotifier = any() ) } returns serialApi @@ -113,7 +120,8 @@ class FSerialDeviceApiWrapperTest { scope = childScope, config = config, serviceFlow = serviceFlow, - serialApiFactory = serialApiFactory + serialApiFactory = serialApiFactory, + flipperActionNotifier = flipperActionNotifier ) val services: ClientBleGattServices = mockk() val serialApi: FSerialDeviceApi = mockk(relaxUnitFun = true) @@ -122,7 +130,8 @@ class FSerialDeviceApiWrapperTest { serialApiFactory.build( config = eq(config), services = eq(services), - scope = any() + scope = any(), + flipperActionNotifier = any() ) } returns null @@ -148,7 +157,8 @@ class FSerialDeviceApiWrapperTest { scope = childScope, config = config, serviceFlow = serviceFlow, - serialApiFactory = serialApiFactory + serialApiFactory = serialApiFactory, + flipperActionNotifier = flipperActionNotifier ) val services: ClientBleGattServices = mockk() val serialApi: FSerialDeviceApi = mockk(relaxUnitFun = true) @@ -157,7 +167,8 @@ class FSerialDeviceApiWrapperTest { serialApiFactory.build( config = eq(config), services = eq(services), - scope = any() + scope = any(), + flipperActionNotifier = any() ) } returns serialApi diff --git a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialOverflowThrottlerTest.kt b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialOverflowThrottlerTest.kt index f4d049930b..83b2cae4ef 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialOverflowThrottlerTest.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/FSerialOverflowThrottlerTest.kt @@ -2,6 +2,7 @@ package com.flipperdevices.bridge.connection.ble.impl.serial import android.content.Context import android.content.pm.PackageManager +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.impl.serial.FSerialOverflowThrottler import com.flipperdevices.bridge.connection.transport.common.api.serial.FSerialDeviceApi import io.mockk.coEvery @@ -29,11 +30,13 @@ class FSerialOverflowThrottlerTest { private lateinit var overflowCharacteristic: ClientBleGattCharacteristic private lateinit var serialApi: FSerialDeviceApi private lateinit var context: Context + private lateinit var flipperActionNotifier: FlipperActionNotifier @Before fun setUp() { serialApi = mockk(relaxUnitFun = true) overflowByteArrayFlow = MutableSharedFlow(replay = 1) + flipperActionNotifier = mockk(relaxed = true) overflowCharacteristic = mockk(relaxUnitFun = true) { coEvery { getNotifications(any(), any()) } returns overflowByteArrayFlow coEvery { read() } returns DataByteArray() @@ -50,7 +53,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -76,7 +80,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val result = runCatching { @@ -103,7 +108,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -140,7 +146,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -164,7 +171,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -200,7 +208,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -245,7 +254,8 @@ class FSerialOverflowThrottlerTest { scope = childScope, serialApi = serialApi, overflowCharacteristic = overflowCharacteristic, - context = context + context = context, + flipperActionNotifier = flipperActionNotifier ) val byteBuffer = ByteBuffer.wrap(ByteArray(4)) byteBuffer.putInt(1024) @@ -281,4 +291,61 @@ class FSerialOverflowThrottlerTest { childScope.cancel() } + + @Test + fun `when send multiple async requests then no bytes missing`() = runTest { + val childScope = TestScope(this.testScheduler) + val serialDeviceApi = FSerialOverflowThrottler( + scope = childScope, + serialApi = serialApi, + overflowCharacteristic = overflowCharacteristic, + context = context, + flipperActionNotifier = flipperActionNotifier + ) + + fun getByteArray() = ByteArray(4) + .let(ByteBuffer::wrap) + .apply { putInt(1024) } + .array() + .let(::DataByteArray) + + fun indexFilledByteArray(size: Int) = ByteArray( + size = size, + init = { index -> index.toByte() } + ) + + overflowByteArrayFlow.emit(getByteArray()) + // 2500 in total + // Need to be delivered separately and async + listOf( + 512, + 512, + 512, + 512, + 452 + ).forEach { size -> launch { serialDeviceApi.sendBytes(indexFilledByteArray(size)) } } + // First portion of 1024 + childScope.advanceUntilIdle() + childScope.advanceTimeBy(100L) // Timeout for reading buffer + childScope.advanceUntilIdle() + overflowByteArrayFlow.emit(getByteArray()) + + // Second portion of 1024 + childScope.advanceUntilIdle() + childScope.advanceTimeBy(100L) // Timeout for reading buffer + childScope.advanceUntilIdle() + overflowByteArrayFlow.emit(getByteArray()) + + // Last portion of 452 + childScope.advanceUntilIdle() + childScope.advanceTimeBy(100L) // Timeout for reading buffer + childScope.advanceUntilIdle() + overflowByteArrayFlow.emit(getByteArray()) + + coVerify { serialApi.sendBytes(eq(indexFilledByteArray(1024))) } + coVerify { serialApi.sendBytes(eq(indexFilledByteArray(1024))) } + coVerify { serialApi.sendBytes(eq(indexFilledByteArray(452))) } + + childScope.cancel() + } } diff --git a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/SerialApiFactoryTest.kt b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/SerialApiFactoryTest.kt index 8fdaa79831..48218025b4 100644 --- a/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/SerialApiFactoryTest.kt +++ b/components/bridge/connection/transport/ble/impl/src/androidUnitTest/kotlin/com/flipperdevices/bridge/connection/ble/impl/serial/SerialApiFactoryTest.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.ble.impl.serial +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import com.flipperdevices.bridge.connection.transport.ble.api.FBleDeviceSerialConfig import com.flipperdevices.bridge.connection.transport.ble.api.OverflowControlConfig import com.flipperdevices.bridge.connection.transport.ble.impl.serial.FSerialOverflowThrottler @@ -11,6 +12,7 @@ import kotlinx.coroutines.CoroutineScope import no.nordicsemi.android.kotlin.ble.client.main.service.ClientBleGattCharacteristic import no.nordicsemi.android.kotlin.ble.client.main.service.ClientBleGattService import org.junit.Assert +import org.junit.Before import org.junit.Test import java.util.UUID @@ -23,6 +25,13 @@ private val config = FBleDeviceSerialConfig( ) class SerialApiFactoryTest { + private lateinit var flipperActionNotifier: FlipperActionNotifier + + @Before + fun setUp() { + flipperActionNotifier = mockk(relaxed = true) + } + @Test fun `return null if serial service is empty`() { val underTest = SerialApiFactory( @@ -32,6 +41,7 @@ class SerialApiFactoryTest { val result = underTest.build( config = config, scope = mockk(), + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) @@ -53,6 +63,7 @@ class SerialApiFactoryTest { val result = underTest.build( config = config, scope = mockk(), + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) @@ -81,6 +92,7 @@ class SerialApiFactoryTest { val result = underTest.build( config = config, scope = mockk(), + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) @@ -111,7 +123,8 @@ class SerialApiFactoryTest { invoke( rxCharacteristic = eq(rxCharacteristic), txCharacteristic = eq(txCharacteristic), - scope = eq(scope) + scope = eq(scope), + flipperActionNotifier = flipperActionNotifier, ) } returns unsafeApiImpl }, @@ -121,6 +134,7 @@ class SerialApiFactoryTest { val result = underTest.build( config = config, scope = scope, + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) @@ -152,7 +166,8 @@ class SerialApiFactoryTest { invoke( rxCharacteristic = eq(rxCharacteristic), txCharacteristic = eq(txCharacteristic), - scope = eq(scope) + scope = eq(scope), + flipperActionNotifier = flipperActionNotifier, ) } returns unsafeApiImpl }, @@ -164,6 +179,7 @@ class SerialApiFactoryTest { overflowControl = OverflowControlConfig(UUID.fromString("00000000-0000-0000-0000-000000000005")) ), scope = scope, + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) @@ -199,7 +215,8 @@ class SerialApiFactoryTest { invoke( rxCharacteristic = eq(rxCharacteristic), txCharacteristic = eq(txCharacteristic), - scope = eq(scope) + scope = eq(scope), + flipperActionNotifier = flipperActionNotifier, ) } returns unsafeApiImpl }, @@ -208,7 +225,8 @@ class SerialApiFactoryTest { invoke( serialApi = eq(unsafeApiImpl), scope = eq(scope), - overflowCharacteristic = eq(overflowCharacteristic) + overflowCharacteristic = eq(overflowCharacteristic), + flipperActionNotifier = flipperActionNotifier, ) } returns throttlerApi } @@ -219,6 +237,7 @@ class SerialApiFactoryTest { overflowControl = OverflowControlConfig(UUID.fromString("00000000-0000-0000-0000-000000000005")) ), scope = scope, + flipperActionNotifier = flipperActionNotifier, services = mockk { every { findService(eq(UUID.fromString("00000000-0000-0000-0000-000000000001"))) diff --git a/components/bridge/connection/transport/common/api/build.gradle.kts b/components/bridge/connection/transport/common/api/build.gradle.kts index 17f228315d..95e53e5f80 100644 --- a/components/bridge/connection/transport/common/api/build.gradle.kts +++ b/components/bridge/connection/transport/common/api/build.gradle.kts @@ -6,5 +6,7 @@ plugins { android.namespace = "com.flipperdevices.bridge.connection.transport.common.api" commonDependencies { + implementation(projects.components.bridge.connection.feature.actionnotifier.api) + implementation(libs.kotlin.coroutines) } diff --git a/components/bridge/connection/transport/common/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/transport/common/api/serial/FSerialDeviceApi.kt b/components/bridge/connection/transport/common/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/transport/common/api/serial/FSerialDeviceApi.kt index 261e10611a..b0bc2e5c52 100644 --- a/components/bridge/connection/transport/common/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/transport/common/api/serial/FSerialDeviceApi.kt +++ b/components/bridge/connection/transport/common/api/src/commonMain/kotlin/com/flipperdevices/bridge/connection/transport/common/api/serial/FSerialDeviceApi.kt @@ -1,5 +1,6 @@ package com.flipperdevices.bridge.connection.transport.common.api.serial +import com.flipperdevices.bridge.connection.feature.actionnotifier.api.FlipperActionNotifier import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -7,4 +8,5 @@ interface FSerialDeviceApi { suspend fun getSpeed(): StateFlow suspend fun getReceiveBytesFlow(): Flow suspend fun sendBytes(data: ByteArray) + fun getActionNotifier(): FlipperActionNotifier } diff --git a/settings.gradle.kts b/settings.gradle.kts index 3819e4f8a0..ee133696a3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -82,6 +82,7 @@ include( ":components:bridge:connection:feature:protocolversion:impl", ":components:bridge:connection:feature:lagsdetector:api", ":components:bridge:connection:feature:lagsdetector:impl", + ":components:bridge:connection:feature:actionnotifier:api", ":components:bridge:connection:feature:serialspeed:api", ":components:bridge:connection:feature:serialspeed:impl",