Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround for Flipper connection mac change #882

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# 1.7.2 - In Progress

- [Feature] Fallback connection by flipper name
- [FIX] Distinct fap items by id in paging sources

# 1.7.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ interface FlipperScanner {
/**
* @return flipper by id
*/
fun findFlipperById(deviceId: String): Flow<DiscoveredBluetoothDevice>
suspend fun findFlipperById(deviceId: String): Flow<DiscoveredBluetoothDevice>

/**
* @return flipper by name
*/
fun findFlipperByName(deviceName: String): Flow<DiscoveredBluetoothDevice>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.flipperdevices.bridge.api.utils

import com.flipperdevices.core.data.SemVer
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.seconds

object Constants {
const val DEVICENAME_PREFIX = "Flipper"
Expand Down Expand Up @@ -76,8 +76,8 @@ object Constants {
}

object BLE {
private const val CONNECT_TIME_SEC = 3L
val CONNECT_TIME_MS = TimeUnit.MILLISECONDS.convert(CONNECT_TIME_SEC, TimeUnit.SECONDS)
val CONNECT_TIME = 3.seconds
val NEW_CONNECT_TIME = 15.seconds
const val RECONNECT_COUNT = 1
const val RECONNECT_TIME_MS = 100L
const val MAX_MTU = 512
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,14 @@ class FlipperBleManagerImpl @Inject constructor(
}

override suspend fun connectToDevice(device: BluetoothDevice) {
info { "Schedule to connect: $device" }
withLock(bleMutex, "connect") {
connectRequest?.cancelPendingConnection()

val connectRequestLocal = connect(device).retry(
Constants.BLE.RECONNECT_COUNT,
Constants.BLE.RECONNECT_TIME_MS.toInt()
).useAutoConnect(true)
)

connectRequestLocal.enqueue()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.bridge.impl.scanner

import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.content.pm.PackageManager
Expand All @@ -16,6 +17,7 @@ import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
Expand Down Expand Up @@ -77,7 +79,7 @@ class FlipperScannerImpl @Inject constructor(
}
}

override fun findFlipperById(deviceId: String): Flow<DiscoveredBluetoothDevice> {
override suspend fun findFlipperById(deviceId: String): Flow<DiscoveredBluetoothDevice> {
val bondedDevice = getAlreadyBondedDevices().firstOrNull {
it.address == deviceId
}
Expand All @@ -88,6 +90,24 @@ class FlipperScannerImpl @Inject constructor(
.map { DiscoveredBluetoothDevice(it) }
}

@SuppressLint("MissingPermission")
override fun findFlipperByName(
deviceName: String,
): Flow<DiscoveredBluetoothDevice> = flow {
getAlreadyBondedDevices().filter {
it.name == deviceName
}.forEach {
emit(it)
}

scanner.scanFlow(provideSettings(), provideFilterForDefaultScan())
.filter { it.device.name == deviceName }
.map { DiscoveredBluetoothDevice(it) }
.collect {
emit(it)
}
}

private fun getAlreadyBondedDevices(): List<DiscoveredBluetoothDevice> {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S &&
ActivityCompat.checkSelfPermission(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,49 @@ class FlipperScannerImplTest {
Assert.assertEquals(bluetoothDevice, foundDevice!!.device)
}

@Test
fun `filter device by name filter`() = runTest {
val bluetoothDevice = mockk<BluetoothDevice> {
every { name } returns "Flipper Dumper"
every { address } returns ""
}

val sr = ScanResult(bluetoothDevice, 0, 0, 0, 0, 0, 0, 0, null, 0L)

every { scanner.scanFlow(any(), any()) } returns flowOf(sr)
every { bluetoothAdapter.bondedDevices } returns emptySet()

val flipperDevices = flipperScanner.findFlipperByName("Flipper Dumper").first()

val foundDevice = flipperDevices
Assert.assertNotNull(foundDevice)
Assert.assertEquals(bluetoothDevice, foundDevice.device)
}

@Test
fun `filter device by name filter and return only this one`() = runTest {
val bluetoothDevice = mockk<BluetoothDevice> {
every { name } returns "Flipper Dumper"
every { address } returns ""
}
val wrongBluetoothDevice = mockk<BluetoothDevice> {
every { name } returns "Flipper Wrong"
every { address } returns ""
}

val sr = ScanResult(bluetoothDevice, 0, 0, 0, 0, 0, 0, 0, null, 0L)
val srWrong = ScanResult(wrongBluetoothDevice, 0, 0, 0, 0, 0, 0, 0, null, 0L)

every { scanner.scanFlow(any(), any()) } returns flowOf(sr,srWrong)
every { bluetoothAdapter.bondedDevices } returns emptySet()

val flipperDevices = flipperScanner.findFlipperByName("Flipper Dumper").toList()
Assert.assertEquals(flipperDevices.size, 1)
val foundDevice = flipperDevices.first()
Assert.assertNotNull(foundDevice)
Assert.assertEquals(bluetoothDevice, foundDevice.device)
}

@Test
fun `block device with incorrect name`() = runTest {
val bluetoothDevice = mockk<BluetoothDevice> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package com.flipperdevices.bridge.service.impl
import androidx.datastore.core.DataStore
import com.flipperdevices.bridge.api.di.FlipperBleServiceGraph
import com.flipperdevices.bridge.api.manager.FlipperBleManager
import com.flipperdevices.bridge.api.manager.ktx.state.ConnectionState
import com.flipperdevices.bridge.service.api.FlipperServiceApi
import com.flipperdevices.bridge.service.impl.delegate.FlipperSafeConnectWrapper
import com.flipperdevices.bridge.service.impl.delegate.connection.FlipperConnectionInformationApiWrapper
import com.flipperdevices.bridge.service.impl.model.SavedFlipperConnectionInfo
import com.flipperdevices.core.di.SingleIn
import com.flipperdevices.core.di.provideDelegate
import com.flipperdevices.core.ktx.jre.FlipperDispatchers
Expand All @@ -17,9 +20,8 @@ import com.flipperdevices.core.preference.pb.PairSettings
import com.flipperdevices.unhandledexception.api.UnhandledExceptionApi
import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import java.util.concurrent.atomic.AtomicBoolean
Expand Down Expand Up @@ -47,7 +49,12 @@ class FlipperServiceApiImpl @Inject constructor(
private val mutex = Mutex()
private var disconnectForced = false

override val connectionInformationApi = bleManager.connectionInformationApi
override val connectionInformationApi by lazy {
FlipperConnectionInformationApiWrapper(
flipperConnectionSource = bleManager.connectionInformationApi,
safeConnectWrapper = flipperSafeConnectWrapper
)
}
override val requestApi = bleManager.flipperRequestApi
override val flipperInformationApi = bleManager.informationApi
override val flipperVersionApi = bleManager.flipperVersionApi
Expand All @@ -61,13 +68,34 @@ class FlipperServiceApiImpl @Inject constructor(

var previousDeviceId: String? = null
scope.launch(FlipperDispatchers.workStealingDispatcher) {
pairSettingsStore.data.map { it.deviceId }.collectLatest { deviceId ->
combine(
bleManager.connectionInformationApi
.getConnectionStateFlow(),
pairSettingsStore.data
) { connectionState, pairSetting ->
(connectionState is ConnectionState.Disconnected) to SavedFlipperConnectionInfo.build(
pairSetting
)
}.collect { (isDeviceDisconnected, connectionInfo) ->
withLock(mutex, "connect") {
if (!unhandledExceptionApi.isBleConnectionForbiddenFlow().first() &&
deviceId != previousDeviceId
) {
previousDeviceId = deviceId
flipperSafeConnectWrapper.onActiveDeviceUpdate(deviceId)
if (unhandledExceptionApi.isBleConnectionForbiddenFlow().first()) {
return@withLock
}

if (previousDeviceId != connectionInfo?.id) { // Reconnect
info { "Reconnect because device id changed" }
flipperSafeConnectWrapper.onActiveDeviceUpdate(
connectionInfo,
force = true
)
previousDeviceId = connectionInfo?.id
} else if (isDeviceDisconnected && !disconnectForced && connectionInfo != null) { // Autoreconnect
info { "Reconnect because device is disconnected, but not forced" }
flipperSafeConnectWrapper.onActiveDeviceUpdate(
connectionInfo,
force = false
)
previousDeviceId = connectionInfo?.id
}
}
}
Expand All @@ -82,23 +110,33 @@ class FlipperServiceApiImpl @Inject constructor(
info { "Failed soft connect, because ble connection forbidden" }
return@launchWithLock
}
if (bleManager.isConnected() || flipperSafeConnectWrapper.isTryingConnected()) {
if (bleManager.isConnected() || flipperSafeConnectWrapper.isConnectingFlow().first()) {
info { "Skip soft connect because device already in connecting or connected stage" }
return@launchWithLock
}
val deviceId = pairSettingsStore.data.first().deviceId
flipperSafeConnectWrapper.onActiveDeviceUpdate(deviceId)

val pairSetting = pairSettingsStore.data.first()
val connectionInfo = SavedFlipperConnectionInfo.build(pairSetting)
info { "Start soft connect to $connectionInfo" }

flipperSafeConnectWrapper.onActiveDeviceUpdate(connectionInfo, force = true)
}

override suspend fun disconnect(isForce: Boolean) = withLock(mutex, "disconnect") {
if (isForce) {
disconnectForced = true
}
flipperSafeConnectWrapper.onActiveDeviceUpdate(null)
flipperSafeConnectWrapper.onActiveDeviceUpdate(null, force = true)
}

override suspend fun reconnect() = withLock(mutex, "reconnect") {
val deviceId = pairSettingsStore.data.first().deviceId
flipperSafeConnectWrapper.onActiveDeviceUpdate(deviceId)
disconnectForced = false
val pairSetting = pairSettingsStore.data.first()

flipperSafeConnectWrapper.onActiveDeviceUpdate(
SavedFlipperConnectionInfo.build(pairSetting),
force = true
)
}

suspend fun close() = withLock(mutex, "close") {
Expand Down
Loading
Loading