Skip to content

Commit

Permalink
Device list connect and disconnect button, settings thread refactorin…
Browse files Browse the repository at this point in the history
…g, fix radar duplicate events
  • Loading branch information
NiroDeveloper committed Jun 1, 2024
1 parent 7f37193 commit a889a46
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.niro.cameraremote.R
import dev.niro.cameraremote.bluetooth.enums.ConnectionState
import dev.niro.cameraremote.bluetooth.helper.BluetoothPermission
import dev.niro.cameraremote.bluetooth.helper.sendKeyboardPress
import dev.niro.cameraremote.bluetooth.helper.toDebugString
import dev.niro.cameraremote.bluetooth.helper.toDeviceWrapper
import dev.niro.cameraremote.interfaces.IConnectionStateCallback
import dev.niro.cameraremote.interfaces.IServiceStateCallback
Expand Down Expand Up @@ -72,12 +73,11 @@ object BluetoothController {
fun takePicture() {
Log.i(null, "Taking picture now")

try {
val hidDevice = bluetoothCallback?.hidDevice
val connectedDevices = hidDevice?.connectedDevices
val localHidDevice = bluetoothCallback?.hidDevice ?: return

connectedDevices?.forEach { bluetoothDevice ->
bluetoothDevice.sendKeyboardPress(hidDevice, 40.toByte())
try {
bluetoothCallback?.getDevices()?.forEach { bluetoothDevice ->
bluetoothDevice.sendKeyboardPress(localHidDevice, 40.toByte())

Log.i(null, "Sent report signal to device: ${bluetoothDevice.name}")
}
Expand Down Expand Up @@ -126,19 +126,53 @@ object BluetoothController {

val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager.adapter
val bluetoothCallback = BluetoothServiceCallback(uiCallbackProxy, uiCallbackProxy)
val newBluetoothCallback = BluetoothServiceCallback(uiCallbackProxy, uiCallbackProxy)

val buildSuccessful = bluetoothAdapter.getProfileProxy(context, bluetoothCallback, BluetoothProfile.HID_DEVICE)
val buildSuccessful = bluetoothAdapter.getProfileProxy(context, newBluetoothCallback, BluetoothProfile.HID_DEVICE)

if (buildSuccessful) {
Log.i(null, "Registered bluetooth service callback for hid device")

this.bluetoothCallback = bluetoothCallback
bluetoothCallback = newBluetoothCallback
} else {
Log.e(null, "BluetoothAdapter.getProfileProxy failed")

uiCallbackProxy.onServiceError(R.string.error_adapter_register)
}
}

@WorkerThread
fun connectDevice(device: DeviceWrapper) {
val hostHidDevice = bluetoothCallback?.hidDevice ?: return

bluetoothCallback?.let { bluetoothCallback ->
val hidDevice = bluetoothCallback.getDevices().firstOrNull { it.address == device.address } ?: return

Log.i(null, "Connect with device: ${hidDevice.toDebugString(hostHidDevice)}")

try {
hostHidDevice.connect(hidDevice)
} catch (ex: SecurityException) {
Log.wtf(null, "Failed connect: $ex")
}
}
}

@WorkerThread
fun disconnectDevice(device: DeviceWrapper) {
val hostHidDevice = bluetoothCallback?.hidDevice ?: return

bluetoothCallback?.let { bluetoothCallback ->
val hidDevice = bluetoothCallback.getDevices().firstOrNull { it.address == device.address } ?: return

Log.i(null, "Disconnect with device: ${hidDevice.toDebugString(hostHidDevice)}")

try {
hostHidDevice.disconnect(hidDevice)
} catch (ex: SecurityException) {
Log.wtf(null, "Failed disconnect: $ex")
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ class BluetoothServiceCallback(

private var hidCallback: HidDeviceCallback? = null

private var appRegistered = false
var appRegistered = false
private set

private val radarDeviceRegister = mutableMapOf<String, ConnectionState>()

override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
Log.d(null, "onServiceConnected($profile, $proxy)")
Expand Down Expand Up @@ -58,7 +61,7 @@ class BluetoothServiceCallback(
}

private fun registerApp(registerHidDevice: BluetoothHidDevice): HidDeviceCallback {
val serviceStateListener = object : IServiceStateCallback {
val serviceStateListener = object : IServiceStateCallback, IConnectionStateCallback {
override fun onServiceStateChange(available: Boolean) {
appRegistered = available

Expand All @@ -74,9 +77,15 @@ class BluetoothServiceCallback(
override fun onServiceError(message: Int) {
serviceStateListener.onServiceError(message)
}

override fun onConnectionStateChange(device: BluetoothDevice, state: ConnectionState) {
connectionStateListener.onConnectionStateChange(device, state)

radarDeviceRegister[device.getAddressString()] = state
}
}

val newHidCallback = HidDeviceCallback(registerHidDevice, connectionStateListener, serviceStateListener)
val newHidCallback = HidDeviceCallback(registerHidDevice, serviceStateListener, serviceStateListener)

try {
registerHidDevice.registerApp(
Expand All @@ -99,26 +108,26 @@ class BluetoothServiceCallback(
val thread = Thread {
Log.i(null, "Starting radar thread")

val foundDeviceAddresses = mutableMapOf<String, ConnectionState>()
while (true) {
val localHidDevice = hidDevice ?: return@Thread
val newDeviceList = getDevices()

for (device in newDeviceList) {
val deviceAddress = device.getAddressString()
val connectionState = device.getConnectionStateEnum(localHidDevice)
val oldConnectionState = radarDeviceRegister[deviceAddress]
val newConnectionState = device.getConnectionStateEnum(localHidDevice)

if (foundDeviceAddresses[deviceAddress] == connectionState) {
if (oldConnectionState == newConnectionState) {
continue
}

Log.d(null, "Radar detected device change: $device")
Log.i(null, "Radar detected device change: $device ($oldConnectionState -> $newConnectionState)")

connectionStateListener.onConnectionStateChange(device, connectionState)
foundDeviceAddresses[deviceAddress] = connectionState
connectionStateListener.onConnectionStateChange(device, newConnectionState)
radarDeviceRegister[deviceAddress] = newConnectionState
}

val sleepDelay = if (foundDeviceAddresses.isEmpty()) 1_000L else 10_000L
val sleepDelay = if (radarDeviceRegister.isEmpty()) 1_000L else 10_000L
Thread.sleep(sleepDelay)
}
}
Expand All @@ -128,8 +137,8 @@ class BluetoothServiceCallback(
}

fun startAutoConnect() {
val connectHidDevice = hidDevice
if (connectHidDevice == null) {
val hostHidDevice = hidDevice
if (hostHidDevice == null) {
Log.e(null, "Bluetooth service is not connected")
serviceStateListener.onServiceError(R.string.error_service_register)

Expand Down Expand Up @@ -159,9 +168,9 @@ class BluetoothServiceCallback(

try {
devices.forEach { device ->
Log.i(null, "Connect with device: ${device.toDebugString(connectHidDevice)}")
Log.i(null, "Connect with device: ${device.toDebugString(hostHidDevice)}")

connectHidDevice.connect(device)
hostHidDevice.connect(device)
}
} catch (ex: SecurityException) {
Log.wtf(null, "Failed auto connect: $ex")
Expand All @@ -170,7 +179,7 @@ class BluetoothServiceCallback(

fun isDeviceConnected() = getDevices(BluetoothProfile.STATE_CONNECTED).isNotEmpty()

private fun getDevices(
fun getDevices(
vararg states: Int = intArrayOf(
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ class HidDeviceCallback(

Log.d(null, "onConnectionStateChanged($device, $state)")

if (device == null) {
return
device?.let {
connectionStateListener.onConnectionStateChange(it, ConnectionState.fromBluetoothProfile(state))
}

connectionStateListener.onConnectionStateChange(device, ConnectionState.fromBluetoothProfile(state))
}

override fun onGetReport(device: BluetoothDevice?, type: Byte, id: Byte, bufferSize: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import dev.niro.cameraremote.ui.pages.DevicesPage
import dev.niro.cameraremote.ui.pages.RemoteLayout
import dev.niro.cameraremote.ui.pages.RemotePage
import dev.niro.cameraremote.ui.pages.SettingsLayout
import dev.niro.cameraremote.ui.pages.SettingsPage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -51,6 +52,7 @@ class MainActivity : ComponentActivity(), AmbientLifecycleObserver.AmbientLifecy

CoroutineScope(Dispatchers.Default).launch {
RemotePage.updateUi()
SettingsPage.loadSettings(this@MainActivity)
BluetoothController.init(this@MainActivity)
}

Expand Down
15 changes: 11 additions & 4 deletions app/src/main/java/dev/niro/cameraremote/ui/pages/DevicesPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Text
import androidx.wear.tooling.preview.devices.WearDevices
import dev.niro.cameraremote.R
import dev.niro.cameraremote.bluetooth.BluetoothController
import dev.niro.cameraremote.bluetooth.DeviceWrapper
import dev.niro.cameraremote.bluetooth.enums.ConnectionState
import kotlinx.coroutines.flow.MutableStateFlow


Expand Down Expand Up @@ -77,16 +79,22 @@ fun DevicesLayout() {
val bondString = stringResource(id = device.bond.stringId)

Chip(
onClick = { },
onClick = {
when (device.state) {
ConnectionState.CONNECTED -> BluetoothController.disconnectDevice(device)
ConnectionState.DISCONNECTED -> BluetoothController.connectDevice(device)
else -> { }
}
},
label = { Text(text = device.name) },
secondaryLabel = { Text(text = "$stateString, $bondString") },
modifier = Modifier.fillMaxWidth(),
colors = ChipDefaults.secondaryChipColors()
colors = ChipDefaults.secondaryChipColors(),
enabled = (device.state in arrayOf(ConnectionState.DISCONNECTED, ConnectionState.CONNECTED))
)

}


item {
val titleTextId = if (deviceListState.isEmpty()) {
R.string.devices_none_paired
Expand All @@ -105,7 +113,6 @@ fun DevicesLayout() {
colors = ChipDefaults.primaryChipColors()
)
}

}

}
22 changes: 9 additions & 13 deletions app/src/main/java/dev/niro/cameraremote/ui/pages/SettingsPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,17 @@ object SettingsPage {

var vibrationEnabled = mutableStateOf(true)

fun loadSettings(context: Context) {
CoroutineScope(Dispatchers.Default).launch {
val dataStore = context.settingsPreferencesDataStore.data.first()
suspend fun loadSettings(context: Context) {
val dataStore = context.settingsPreferencesDataStore.data.first()

dataStore[VIBRATION_ENABLED_KEY]?.let {
vibrationEnabled.value = it
}
dataStore[VIBRATION_ENABLED_KEY]?.let {
vibrationEnabled.value = it
}
}

fun writeSettings(context: Context) {
CoroutineScope(Dispatchers.Default).launch {
context.settingsPreferencesDataStore.edit { preferences ->
preferences[VIBRATION_ENABLED_KEY] = vibrationEnabled.value
}
suspend fun writeSettings(context: Context) {
context.settingsPreferencesDataStore.edit { preferences ->
preferences[VIBRATION_ENABLED_KEY] = vibrationEnabled.value
}
}

Expand All @@ -67,7 +63,6 @@ object SettingsPage {
@Composable
fun SettingsLayout() {
val context = LocalContext.current
SettingsPage.loadSettings(context)

ScalingLazyColumn(
modifier = Modifier.fillMaxSize()
Expand All @@ -91,7 +86,8 @@ fun SettingsLayout() {
checked = vibrationEnabled,
onCheckedChange = {
vibrationEnabled = it
SettingsPage.writeSettings(context)

CoroutineScope(Dispatchers.Default).launch { SettingsPage.writeSettings(context) }
},
label = { Text(stringResource(id = R.string.vibration)) },
toggleControl = { Switch(checked = vibrationEnabled) },
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@
<string name="bond_state_bonding">Koppeln</string>
<string name="bond_state_unbound">Ungekoppelt</string>
<string name="bond_state_error">Kopplungsfehler</string>
<string name="shutdown">App Herunterfahren</string>
<string name="shutdown_description">Diese App beenden und den Bluettooth Service abmelden.</string>
</resources>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@
<string name="bond_state_bonding">Bonding</string>
<string name="bond_state_unbound">Unbound</string>
<string name="bond_state_error">Bond Error</string>
<string name="shutdown">App Shutdown</string>
<string name="shutdown_description">Stop the app and unregister bluetooth services.</string>
</resources>

0 comments on commit a889a46

Please sign in to comment.