A guide to Android-specific platform restrictions that affect Meshtastic SDK integration, particularly in the context of background connectivity and foreground services.
Status: Required on API 34+ Impact: BLE and USB-serial connections die in the background without a foreground service.
The Meshtastic SDK does not start a foreground service for you. On Android 14+ (API 34+), the OS enforces strict background execution limits that prevent:
- BLE (Bluetooth Low Energy) scans and connections
- USB Host API access
- Persistent socket reads/writes
If you do not declare and start a foreground service, the connection will terminate when your app enters the background.
Wrap your connect() / disconnect() calls in an Activity or Service that declares the appropriate foreground service permissions and type.
<!-- Declare foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<application>
<!-- Declare a foreground service with connectedDevice type -->
<service
android:name=".RadioConnectionService"
android:foregroundServiceType="connectedDevice" />
</application>Your Service must call startForeground(...) before calling client.connect() and stop it in onDestroy() or when explicitly disconnecting:
class RadioConnectionService : Service() {
private val scope = CoroutineScope(Dispatchers.Main + Job())
private lateinit var client: RadioClient
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Start foreground service BEFORE connecting
startForeground(
NOTIFICATION_ID,
buildNotification("Connected to mesh...")
)
// Now safe to connect
scope.launch {
client.connect()
}
return START_STICKY
}
override fun onDestroy() {
scope.launch {
client.disconnect()
}
stopForeground(STOP_FOREGROUND_REMOVE)
scope.cancel()
super.onDestroy()
}
// … rest of service implementation
}Status: Optional concern for SqlDelightStorageProvider
If your app targets targetSdk >= 30, scoped storage rules apply:
- Direct file I/O to
Environment.getExternalStorageDirectory()is blocked. - Prefer
context.filesDir(app-private) orcontext.cacheDirforSqlDelightStorageProvider.baseDir.
Example:
val storage = SqlDelightStorageProvider(baseDir = context.filesDir.absolutePath)See Android Scoped Storage documentation.
Status: Required for USB-serial integration
Android's USB Host API requires explicit user permission before accessing USB devices. See integration guide — Serial (USB) setup for the full UsbManager.requestPermission(...) flow and AndroidSerialPorts API.
Related: ADR-002 (threading), ADR-012 (transport threading), integration guide