Skip to content

Commit 303aa68

Browse files
committed
Create SerialDeviceMonitor.kt
1 parent 16c440c commit 303aa68

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package org.operatorfoundation.transmission
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import android.hardware.usb.UsbManager
8+
import kotlinx.coroutines.flow.StateFlow
9+
import timber.log.Timber
10+
11+
/**
12+
* Application-scoped monitor for USB serial device availability.
13+
*
14+
* Wraps [SerialConnectionFactory] and self-registers a USB broadcast receiver
15+
* using application context, so attach/detach events are handled for the full
16+
* app lifetime without requiring any Activity involvement.
17+
*
18+
* Instantiate once in Application.onCreate() and hold as a property.
19+
* Observe [connectionState] to react to connection changes.
20+
*
21+
* Usage:
22+
* ```
23+
* // In Application.onCreate():
24+
* serialDeviceMonitor = SerialDeviceMonitor(applicationContext)
25+
*
26+
* // Anywhere that needs connection state:
27+
* serialDeviceMonitor.connectionState.collect { state -> ... }
28+
* ```
29+
*/
30+
class SerialDeviceMonitor(private val context: Context)
31+
{
32+
private val factory = SerialConnectionFactory(context)
33+
34+
/** Current serial connection state. Observe this to react to connect/disconnect. */
35+
val connectionState: StateFlow<SerialConnectionFactory.ConnectionState> = factory.connectionState
36+
37+
/** True when a serial connection is established. Convenience derived from [connectionState]. */
38+
val isConnected: Boolean
39+
get() = connectionState.value is SerialConnectionFactory.ConnectionState.Connected
40+
41+
// ==================== USB Broadcast Receiver ====================
42+
43+
private val usbReceiver = object : BroadcastReceiver()
44+
{
45+
override fun onReceive(context: Context, intent: Intent)
46+
{
47+
when (intent.action)
48+
{
49+
UsbManager.ACTION_USB_DEVICE_ATTACHED ->
50+
{
51+
Timber.d("SerialDeviceMonitor: USB device attached")
52+
onDeviceAttached()
53+
}
54+
55+
UsbManager.ACTION_USB_DEVICE_DETACHED ->
56+
{
57+
Timber.d("SerialDeviceMonitor: USB device detached")
58+
factory.onDeviceDetached()
59+
}
60+
}
61+
}
62+
}
63+
64+
init
65+
{
66+
// Register receiver for the app's lifetime using application context.
67+
// No need to unregister — this lives as long as the app does.
68+
val filter = IntentFilter().apply {
69+
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
70+
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
71+
}
72+
73+
context.registerReceiver(usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
74+
Timber.d("SerialDeviceMonitor: USB receiver registered")
75+
76+
// Check for already-connected devices on startup
77+
// (device may have been attached before the app launched)
78+
onDeviceAttached()
79+
}
80+
81+
// ==================== Private ====================
82+
83+
/**
84+
* Called on USB attach or startup. Finds available serial devices
85+
* and initiates connection to the first one found, if any.
86+
*
87+
* No-ops if a connection is already established or in progress.
88+
*/
89+
private fun onDeviceAttached()
90+
{
91+
// Don't attempt if already connected or connecting
92+
val current = connectionState.value
93+
if (current is SerialConnectionFactory.ConnectionState.Connected ||
94+
current is SerialConnectionFactory.ConnectionState.Connecting ||
95+
current is SerialConnectionFactory.ConnectionState.RequestingPermission)
96+
{
97+
Timber.d("SerialDeviceMonitor: connection already active, ignoring attach")
98+
return
99+
}
100+
101+
val devices = factory.findAvailableDevices()
102+
if (devices.isEmpty())
103+
{
104+
Timber.d("SerialDeviceMonitor: no serial devices found")
105+
return
106+
}
107+
108+
Timber.d("SerialDeviceMonitor: found ${devices.size} device(s), connecting to first")
109+
factory.createConnection(devices.first().device)
110+
}
111+
}

0 commit comments

Comments
 (0)