Skip to Content
DocumentationAndroid AppOverview

Android App

The Android app is the node. Everything load-bearing — the socket, the queue, telephony, retries, crypto — lives in a native Kotlin agent hosted by a foreground service. The Flutter layer on top is a control panel that renders what the agent reports.

What runs where

ConcernOwner
Process lifetimeGatewayForegroundService, BootReceiver
OrchestrationAgentController
Backend connectionWebSocketClient, ProtocolCodec, Heartbeat, RestClient
TelephonySmsTransport, SmsSender, SmsReceiver, per-SIM monitors
DurabilityRoom outbox, inbox and event outbox, plus repositories
RecoveryAgentWatchdogWorker, resync
SecurityKeystore, CryptoBox, Redaction, RateLimiter
UIFlutter — pairing, dashboard, messages, logs, settings

All telephony goes directly on top of Android’s own APIs — SmsManager, TelephonyManager, SubscriptionManager, BroadcastReceiver — exposed to Dart through Pigeon-generated channels. Community Flutter SMS plugins are deliberately not used.

The foreground service

A persistent SMS gateway matches none of Android’s narrow standard foreground service types cleanly, so Luno declares specialUse with a documented justification.

On Android 14+, a foreground service started without a valid foregroundServiceType crashes on start. This is not a warning — it is an immediate ForegroundServiceStartNotAllowedException.

The service posts an ongoing notification on a dedicated channel. Dismissing it or lowering its importance gets the service reaped on some OEM skins, which is why the app tells you not to.

Build flavors

FlavorRECEIVE_SMSInbound SMSInstalls clean from any source
fulldeclaredyesno — warns on internet-sideload
sendOnlyabsentnoyes

The split lives in android/app/src/full/AndroidManifest.xml. Native exposes the choice as BuildConfig.RECEIVE_SMS_ENABLED, surfaced to Dart through isReceiveSmsSupported() so the settings screen hides a permission that could never be granted. CI asserts the sendOnly APK genuinely contains no RECEIVE_SMS.

Platform constraints worth knowing before you deploy

  • Background service starts are restricted from Android 12. The service is started from a user action on first launch, and from allowed exemptions such as BOOT_COMPLETED.
  • BOOT_COMPLETED is not guaranteed. It is not delivered to apps that were force-stopped or never launched, and some OEMs delay or withhold it entirely.
  • WorkManager’s periodic floor is 15 minutes, so it can only ever be a backstop — never the reconnection mechanism.
  • SMS permissions are auto-reset for unused apps from Android 11, and hard-restricted for sideloaded apps from Android 15.
  • minSdk is 26, which caps the legacy API surface the code has to branch on.

In this section