A fully offline, end-to-end encrypted Android messaging app that builds its own network over Bluetooth Low Energy — no internet, no servers, no compromise.
WhatsApp, Signal, and Telegram are clients talking to servers. Every message is routed through a data center. When the server is unreachable — the app is silent.
Natural disasters knock out cell towers. Governments enforce internet blackouts during protests. Remote regions never had infrastructure. These are not edge cases — they are recurring realities.
Even when a connection exists, centralized messaging exposes metadata to service providers — who you talk to, when, and how often. Server seizure compromises entire user bases.
Existing offline tools are fragmented: Meshtastic requires $30+ LoRa hardware, Briar lacks voice capability, GoTenna charges subscriptions. None run on the phone you already have.
The core gap: No open-source solution combines BLE mesh networking, military-grade encryption, multimedia transfer, and real-time voice calling in a single Android application — running on commodity hardware, with zero server dependency.
Ungrid transforms a text-only BLE mesh foundation into a complete multimedia communication platform — images, documents, voice notes, and live voice calls — all end-to-end encrypted, all fully offline.
Every device is simultaneously a sender and relay. Messages relay across unlimited hops — reaching anyone in the mesh without a direct connection. Self-organizing, no configuration required.
The Noise XX protocol (same as WhatsApp & WireGuard) for peer sessions. PBKDF2 + AES-256-GCM for group channels. Ed25519 packet signing. PKCS#7 padding defeats traffic analysis.
Images, documents up to 10 MB, and voice notes — all sent over BLE via TLV-encoded packets with automatic fragmentation. Images auto-compressed (EXIF-corrected, 512px, JPEG 85%).
Live bidirectional voice calls over BLE. AAC-LC at 16kHz/20kbps. Full call state machine: IDLE → CALLING → RINGING → IN_CALL → ENDED. Call signaling itself is Noise-encrypted.
100% binary-compatible with the iOS reference implementation. Same V1/V2 packet headers, UUIDs, and message types. Android and iPhone users share the same mesh network seamlessly.
No accounts, phone numbers, or servers. Identity derived from a self-sovereign Noise key pair generated on first launch. Peer ID = first 16 hex chars of SHA-256(public key).
Ungrid shares the same binary wire protocol, BLE UUIDs, and Noise XX cryptography as the iOS reference implementation — ensuring full cross-platform mesh interoperability. Every capability listed below was built on top of that shared foundation without breaking a single byte of compatibility.
| Capability | BitChat (iOS original) | Ungrid Android |
|---|---|---|
| Text messaging | ✓ Supported | ✓ Supported |
| Image sharing | Not available | ✓ EXIF-corrected, 512px, JPEG 85% |
| Document transfer | Not available | ✓ Up to 10 MB, TLV-encoded |
| Voice notes | Not available | ✓ AAC-LC with waveform visualizer |
| Real-time voice calls | Not available | ✓ BLE bidirectional, full state machine |
| Private chat UI | No dedicated screen | ✓ PrivateChatScreen composable |
| Chat interface | IRC-style terminal | ✓ WhatsApp-style bubbles + GUI |
| Message persistence | Session only — lost on close | ✓ Room DB — survives app restart |
| BLE write reliability | WRITE_TYPE_NO_RESPONSE (silent loss) | ✓ WRITE_TYPE_DEFAULT (ACK-based) |
| Binary protocol | ✓ V1 / V2 | ✓ V1 / V2 — 100% compatible |
| iOS ↔ Android interop | iOS only | ✓ Full cross-platform mesh |
Stateless Compose screens driven by StateFlow. ChatViewModel bridges the service layer. AppStateStore is the process-wide reactive state singleton.
Foreground service keeps BLE alive. Central coordinator owns all subsystems. Each peer gets a dedicated coroutine actor for race-condition-free packet processing.
Noise XX per-peer session manager. PBKDF2 + AES-256-GCM for channels. Ed25519 signing with TTL exclusion. EncryptedSharedPreferences for key storage.
Simultaneously GATT Server (peripheral/advertise) and GATT Client (central/scan). Nordic BLE library. 4-tier adaptive power management (3.3%–100% duty cycle).
Each private conversation is protected by a unique Noise XX session. The lock icon and "Encrypted" label confirm an active session key. WhatsApp-style message bubbles replace the original terminal interface.
The broadcast channel reaches every node in the mesh. Packets are signed with Ed25519 but not encrypted — anyone in range can read. The peer list in the sidebar shows all discovered nodes with RSSI indicators.
Live bidirectional voice calls over Bluetooth Low Energy. AAC-LC encoded at 16kHz/20kbps — sufficient for clear speech within BLE's power budget. Call signaling is itself Noise-encrypted.
All conversations — private peers, broadcast channels, and password-protected rooms — in a single persistent list. Chat history survives app restarts via Room Database.
After modifying the BLE write path, the Noise XX handshake was silently failing. Packets were being sent but dropped on the receiver with no error. Root cause: WRITE_TYPE_NO_RESPONSE fires-and-forgets without any acknowledgment. Single character change to WRITE_TYPE_DEFAULT restored ACK-based delivery and full handshake completion.
Every relay node must decrement the TTL to prevent infinite loops — but a naive implementation would invalidate the sender's Ed25519 signature. The solution: the TTL field is deliberately excluded from the signed data. Any relay can safely decrement TTL; the signature still verifies against the original sender's identity. This is a subtle but critical protocol invariant that required careful design.
A 20ms AAC audio frame can exceed 469 bytes (the BLE fragment payload limit), meaning a single audio chunk spans multiple BLE packets. These arrive out-of-order on a mesh. The fragment reassembly timeout (30s) is far too slow for real-time audio. Solved by implementing a dedicated audio reassembly path with a per-call sliding buffer keyed by sequence number.
Android camera apps embed rotation metadata in EXIF rather than rotating the actual pixels. Without correction, every photo sent through the app would appear rotated 90° on the receiver. The fix requires reading the EXIF orientation tag with ExifInterface and applying a Matrix.postRotate() transform before encoding.
The Android emulator cannot simulate BLE scanning or advertising. Every meaningful end-to-end test required two physical phones. Cross-platform compatibility testing with the iOS reference implementation required borrowing an iPhone. This limited iteration speed and made CI/CD impossible for BLE-specific features.
Real-time voice calling was nearly descoped due to the audio fragmentation complexity. Implemented in the final two weeks of development. The key insight was treating the call as a lightweight state machine with a separate audio reassembly pipeline rather than routing call audio through the general packet processor.
Buffer and forward audio packets for peers not currently in BLE range — extending voice notes to true store-and-forward.
Coordinate multi-party audio sessions via a distributed call coordinator — point-to-point only today.
ACK packets for message delivery confirmation across mesh relays — today best-effort only.
Partial fragment retry on interrupted large file transfers — currently requires a full resend.
External formal verification of the custom Noise XX implementation before production deployment.
Pre-deployed mesh relay nodes (Raspberry Pi / cheap Android) for disaster zones — first responders connect instantly.
A native iOS counterpart bringing images, documents, voice notes, and live voice calling to iPhone users — full feature parity with this Android fork over the shared binary protocol.