# How Bitchat Android Maintains Persistent Background Connectivity

> Discover how Bitchat Android ensures persistent background connectivity using a layered architecture with Bluetooth LE, Wi-Fi Aware, and smart maintenance loops for reliable communication.

- Repository: [permissionlesstech/bitchat-android](https://github.com/permissionlesstech/bitchat-android)
- Tags: internals
- Published: 2026-08-04

---

**Bitchat Android maintains persistent background connectivity through a layered architecture that combines a unified orchestration service with transport-specific implementations for Bluetooth Low-Energy and Wi-Fi Aware, backed by power-profile-driven maintenance loops and keep-alive messaging.**

The `permissionlesstech/bitchat-android` repository implements a **mesh-wide, always-on connection** that remains active even when the user interface is closed. This is achieved by coordinating three tightly-integrated layers: transport selection, transport services, and a shared mesh core that provides common utilities like gossip synchronization and peer management.

## Transport Selection and Unified Orchestration

The `UnifiedMeshService` class serves as the **central façade** that the UI and higher-level code interact with. Located in [`app/src/main/java/com/bitchat/android/mesh/UnifiedMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/UnifiedMeshService.kt), this service decides at runtime whether to use BLE or Wi-Fi Aware and forwards all traffic to whichever transport is active for a given peer.

When `startServices()` is invoked, the service initializes both transport layers conditionally:

```kotlin
if (isBleEnabled()) {
    bluetooth.startServices()
} else {
    bluetooth.setBleTransportEnabled(false)
}
WifiAwareController.startIfPossible()

```

The method also launches a **periodic announcement scheduler** that keeps the mesh discoverable. This scheduler reads the current power profile and broadcasts mesh announcements at the configured interval whenever direct peers exist:

```kotlin
serviceScope.launch {
    powerManager.profile
        .map { it.meshAnnouncementIntervalMs to it.hasDirectPeers }
        .collectLatest { (intervalMs, hasRecipients) ->
            while (isActive) {
                delay(intervalMs)
                if (powerManager.profile.value.hasDirectPeers) sendBroadcastAnnounce()
            }
        }
}

```

All outgoing methods—`sendMessage`, `sendPrivateMessage`, `sendFileBroadcast`, and others—route traffic based on peer-specific readiness checks (`isBleReady`, `isWifiReady`) defined in the target peer's metadata.

## BLE Transport: Foreground-Style Background Service

The BLE implementation in [`BluetoothMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothMeshService.kt) operates as a **persistent background service** that survives UI detachment. It registers with the `TransportBridgeService` to enable cross-transport packet forwarding:

```kotlin
TransportBridgeService.register("BLE", this)

```

The service maintains a long-living coroutine scope for I/O operations:

```kotlin
CoroutineScope(Dispatchers.IO + SupervisorJob())

```

This scope processes inbound voice frames, background direct message notifications, and periodic debug logging without requiring an active activity.

When a private message arrives while `delegate` is `null` (indicating the UI is backgrounded or destroyed), the service posts a system notification:

```kotlin
if (delegate == null && message.isPrivate && message.sender != "system") {
    serviceNotificationManager.setAppBackgroundState(true)
    serviceNotificationManager.showPrivateMessageNotification(...)
}

```

Peer lifecycle management includes a **grace period mechanism** to handle transient disconnects. The constant `PEER_DISCONNECT_GRACE_MS` defines how long to wait before removing a peer's Noise session:

```kotlin
delay(PEER_DISCONNECT_GRACE_MS)
if (!linkBack && !seenAfterDisconnect) {
    peerManager.removePeer(peerID)
}

```

This prevents stale sessions from accumulating while tolerating brief connectivity fluctuations common in mobile environments.

## Wi-Fi Aware Transport: Resilient Peer-to-Peer Links

The Wi-Fi Aware implementation in [`WifiAwareMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/WifiAwareMeshService.kt) creates **publish and subscribe discovery sessions** that autonomously maintain connectivity through several resilience mechanisms.

### Periodic Connection Maintenance

The `startPeriodicConnectionMaintenance()` method runs a background loop that:
- Prunes stale discovery entries
- Detects peers with no active socket and attempts reconnection
- Refreshes discovery sessions after extended idle periods

### Server-Ready Handshake

When a peer requests role reversal or a client connection, the service opens a TCP server socket, advertises the port via a `NetworkSpecifier`, and accepts connections bound to the Wi-Fi Aware network:

```kotlin
// Server socket creation and network binding
val serverSocket = ServerSocket(0) // System-assigned port
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(session, peerHandle)
    .setPort(serverSocket.localPort)
    .build()
// Connection tracked for keep-alive management
connectionTracker.addConnection(socket, peerId)

```

### Client Connection with Retry Logic

The client side handles connection establishment with exponential back-off through `connectAwareClientSocket`. If repeated attempts fail, it requests a role reversal to switch which peer acts as server.

### Keep-Alive Messaging

Both sides send periodic empty payloads to prevent underlying data path expiration:
- `sendSubscribePing()` on the subscriber side
- `sendServerReadyPayload()` on the publisher side

These messages maintain the `WifiAwareNetworkInfo` association without consuming significant bandwidth.

## Power Management Integration

Background connectivity behavior is governed by a `PowerManager` singleton that supplies dynamic **power profiles**. These profiles define:

| Parameter | Purpose |
|-----------|---------|
| `meshAnnouncementIntervalMs` | Cadence for mesh-wide broadcast announcements |
| `wifiAware.discoverySessionRefreshMinMs` | Minimum lifetime before Wi-Fi Aware discovery refresh |
| `wifiAware.connectionMaintenanceMs` | Interval for connection maintenance loop execution |

All background coroutines consume `PowerManager.getInstance(context).profile` as a `StateFlow`, enabling runtime adjustment of battery consumption characteristics without service restart.

## End-to-End Background Message Flow

Understanding how a message is processed while the app is backgrounded demonstrates the integration of these components:

1. **Packet reception** — BLE or Wi-Fi Aware receives a `BitchatPacket` through the respective transport's socket handler
2. **Packet processing** — `packetProcessor` in [`MeshCore.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/MeshCore.kt) reassembles fragments if needed
3. **Message handling** — `MessageHandler` validates structure and routes by type
4. **Security validation** — `SecurityManager` verifies signatures and decrypts payloads
5. **Admission control** — `IncomingMessageAdmission.admitToAppState(message)` deduplicates and filters
6. **Dispatch decision** — If `delegate` (UI) is attached, deliver to UI; otherwise trigger `serviceNotificationManager` for system notification
7. **Background persistence** — Announcement scheduler and connection maintenance loops continue executing, keeping the mesh alive for subsequent messages

## Practical Implementation Examples

### Starting the Mesh from Application Code

```kotlin
val unifiedMesh = UnifiedMeshService(
    applicationContext, 
    BluetoothMeshService(applicationContext)
)
unifiedMesh.startServices() // Launches BLE + Wi-Fi Aware, schedules announcements

```

### Sending a Background Private Message

```kotlin
unifiedMesh.sendPrivateMessage(
    content = "Hey, are you there?",
    recipientPeerID = peerId,
    recipientNickname = "Alice"
)
// Notification fires automatically if UI is detached

```

### Clean Shutdown

```kotlin
unifiedMesh.stopServices() // Stops BLE, Wi-Fi Aware, cancels scopes, clears peers

```

### Debugging Peer Connectivity

```kotlin
val peers = unifiedMesh.getPeerNicknames() // Map<PeerID, Nickname>
Log.d("MeshDebug", "Connected peers: $peers")

```

## Key Source Files

| File | Responsibility |
|------|--------------|
| [`UnifiedMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/UnifiedMeshService.kt) | Transport orchestration, announcement scheduling, unified API |
| [`BluetoothMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothMeshService.kt) | BLE transport, background notifications, peer lifecycle |
| [`WifiAwareMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/WifiAwareMeshService.kt) | Wi-Fi Aware discovery, reconnection, keep-alive |
| [`PowerManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/PowerManager.kt) | Dynamic power profiles for background interval tuning |
| [`MeshCore.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/MeshCore.kt) | Shared fragment management, gossip sync, peer lookup |
| [`TransportBridgeService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/TransportBridgeService.kt) | Cross-transport packet forwarding without loops |
| [`MeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/MeshService.kt) | Public interface contract for UI and services |

## Summary

- **UnifiedMeshService** coordinates BLE and Wi-Fi Aware through a single API, automatically routing to the appropriate transport per peer.
- **BluetoothMeshService** runs as a persistent background coroutine scope with foreground-style notification handling for incoming private messages.
- **WifiAwareMeshService** implements resilient peer-to-peer links through publish/subscribe discovery, server-ready handshakes, retry logic with role reversal, and keep-alive messaging.
- **PowerManager profiles** drive all background intervals, enabling runtime battery optimization without service restarts.
- **Grace periods and stale session pruning** prevent resource leaks while tolerating mobile connectivity volatility.

## Frequently Asked Questions

### How does Bitchat Android keep the mesh alive when the app is closed?

The combination of `UnifiedMeshService.startAnnouncementScheduler()` and `WifiAwareMeshService.startPeriodicConnectionMaintenance()` creates **coroutine-based loops that outlive the UI** by operating within the application process scope rather than activity scope. These loops use `Dispatchers.IO` with `SupervisorJob` to survive configuration changes and activity destruction, tied to power-profile intervals that refresh discovery sessions and emit keep-alive announcements.

### What triggers a background notification for new messages?

In [`BluetoothMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothMeshService.kt), the condition `delegate == null && message.isPrivate` triggers `serviceNotificationManager.showPrivateMessageNotification()`. The `delegate` field holds a reference to the active UI component; when null, the app is either backgrounded or destroyed. The same pattern exists in Wi-Fi Aware message handling through `WifiAwareMeshService.handleMessageReceived`.

### How is battery consumption optimized for background operation?

All background loops consume intervals from `PowerManager.getInstance(context).profile`, which exposes `meshAnnouncementIntervalMs` and related timings as reactive state. Users can adjust these through debug UI, and the changes propagate immediately to running coroutines via `StateFlow` collection. This design allows aggressive throttling or near-real-time responsiveness without code changes or service restarts.

### What prevents message loops when both BLE and Wi-Fi Aware are active?

`TransportBridgeService` acts as a **deduplication layer** that assigns each transport a unique identifier ("BLE", "WIFI_AWARE"). When one transport receives a packet, the bridge checks whether that packet was already seen from the other transport before forwarding. This prevents the same message from bouncing between radio interfaces and flooding the mesh, while still allowing either transport to serve as fallback for the other.