Anonymous mesh messaging without Internet
## Murmur 0.2.96 **SHA256:** `21b43169127002ac4731b606690fe282f188ab59a85f6b94a6e336ce2c7aa94b` **Size:** 9307014 bytes ### Verification This APK was built and signed automatically from tag `v0.2.96` by GitHub Actions. You can verify the build by: 1. Checking the [workflow run](https://github.com/De-Novo-Group/rangzen-revival/actions/runs/21495924917) 2. Comparing the SHA256 hash above with `sha256sum murmur-0.2.96.apk` ### Install - Download `murmur-0.2.96.apk` below - Or get it from our [download page](https://murmur-telemetry.denovogroup.org/download) **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.95...v0.2.96
## Murmur 0.2.95 **SHA256:** `abaa20eb1b59fa6457d8079e0debc7af594e3a39acafc7c9b8277bb500a61f9e` **Size:** 9307298 bytes ### Verification This APK was built and signed automatically from tag `v0.2.95` by GitHub Actions. You can verify the build by: 1. Checking the [workflow run](https://github.com/De-Novo-Group/rangzen-revival/actions/runs/21459842464) 2. Comparing the SHA256 hash above with `sha256sum murmur-0.2.95.apk` ### Install - Download `murmur-0.2.95.apk` below - Or get it from our [download page](https://murmur-telemetry.denovogroup.org/download) **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.94...v0.2.95
Includes the critical fix: use publicIdPrefix as peer ID for symmetric BLE initiator selection. Co-Authored-By: Claude Opus 4.5
Release 0.2.93
Release 0.2.92
Co-Authored-By: Claude Opus 4.5
Co-Authored-By: Claude Opus 4.5
Co-Authored-By: Claude Opus 4.5
Co-Authored-By: Claude Opus 4.5
When both devices try to initiate NDP to each other simultaneously, the single-interface device (Pixel 8) can't respond, causing both connections to fail. Fix: Use publicId comparison to coordinate who initiates: - Higher publicId initiates, lower waits to be connected - Exception: devices without responder (single NDI) always initiate - This prevents race conditions while ensuring all connections work Added methods: - WifiAwareManager.getLocalPublicId() - get local ID for comparison - WifiAwareManager.hasResponderCapability() - check if device can accept v0.2.87 Co-Authored-By: Claude Opus 4.5
Devices like Pixel 8 have only 1 NDI (NAN Data-path Interface), while Pixel 4a/5 have 2. Our responder holds an interface waiting for connections. On single-interface devices, this blocks all initiator NDP requests with "no interfaces available" error. Fix: Query Characteristics.numberOfSupportedDataInterfaces (API 31+) and only start responder if device has 2+ interfaces. Single-interface devices can still exchange messages by initiating connections to multi-interface devices. Added comprehensive documentation with AOSP source references: - https://android.googlesource.com/platform/hardware/interfaces/+/master/wifi/1.0/types.hal - https://source.android.com/docs/core/connect/wifi-aware - https://developer.android.com/reference/android/net/wifi/aware/Characteristics v0.2.86 Co-Authored-By: Claude Opus 4.5
- Clear discoveredPeers when session re-attaches (PeerHandles are session-specific) - Add onAwareSessionTerminated callback to clear peers and reset state - Validate PeerHandle in requestNetworkForPeer before NDP request - Prevent using invalid PeerHandle (pubSubId=-128 error) after session termination Root cause: WiFi Aware cluster changes or resource contention can terminate the session. Old PeerHandles from the previous session become invalid, causing NDP requests to fail with "pubSubId=-128" or "no interfaces available". v0.2.85 Co-Authored-By: Claude Opus 4.5
WiFi Aware's onServiceDiscovered fires once per peer (unlike BLE which continuously rescans). Without periodic refresh, WiFi Aware peers went stale after 30 seconds and were pruned from the registry before the exchange loop (20s interval) could use them. Now a keepalive runnable re-reports all discovered WiFi Aware peers to the registry every 15 seconds, keeping lastSeen timestamps fresh until onServiceLost fires. v0.2.84 Co-Authored-By: Claude Opus 4.5
The publicId service data (128-bit UUID + 4 bytes) added to the primary advertisement exceeded the 31-byte BLE ad limit alongside the service UUID, causing silent advertising failure. ADB phones couldn't see each other via BLE. Move publicId prefix to the scan response payload (separate 31 bytes), keeping the primary ad within limits. v0.2.83 Co-Authored-By: Claude Opus 4.5
BLE peers were not merged with LAN/WiFi Aware peers until after exchange completed, causing inflated counts in the interim. Now: - BLE advertiser includes first 4 bytes (8 hex chars) of publicId as service data alongside the service UUID - BLE scanner extracts the prefix from scan results - Peer registry immediately correlates BLE peers with existing LAN/WiFi Aware peers using the prefix match - Also prunes orphaned address map entries and filters stale BLE MACs v0.2.82 Co-Authored-By: Claude Opus 4.5
Two bugs caused incorrect peer counts: 1. onPeersUpdated refreshed ALL BLE peers including stale MACs from rotation, preventing them from ever going stale in the registry. Fix: only re-report BLE peers scanned within the stale threshold. 2. pruneStale removed peers but left orphaned addressToPeerId entries. Fix: prune address map entries pointing to non-existent peers. v0.2.81 Co-Authored-By: Claude Opus 4.5
BLE peers were registered with temporary IDs (temp:ble:MAC) but never correlated with WiFi Aware/LAN peers that use DeviceIdentity publicId. The root cause was that peerDeviceIdHash (SHA-256 of ANDROID_ID) differs from publicId (SHA-256 of public key), so the merge never matched. - Add publicId field to BLE exchange protocol (client message) - Send DeviceIdentity.getDeviceId() in both client and server BLE exchange - Extract peer's publicId from exchange response for correlation - Enhance updatePeerIdAfterHandshake with prefix matching (WiFi Aware uses 8-char prefix, LAN uses full 16-char ID) v0.2.80 Co-Authored-By: Claude Opus 4.5
Adds dumpPeers() using android.util.Log (visible in release builds) to diagnose remaining cross-transport deduplication issues. Called every 20s before exchange attempts. Co-Authored-By: Claude Opus 4.5
WiFi Aware data paths require both sides to call requestNetwork(). The publisher (responder) was missing a standing network accept, causing incoming NDP requests to be rejected with "can't find a request with specified pubSubId". Added startResponderAccept() on the publish side and wired up onIncomingConnection callback to run LAN exchanges over WiFi Aware data paths. Co-Authored-By: Claude Opus 4.5
WiFi Aware broadcast receiver registration lacked RECEIVER_NOT_EXPORTED flag required on Android 12+ (API 33), causing silent SecurityException that prevented all WiFi Aware discovery and exchange. LAN peer registration now performs cross-transport correlation using publicId, matching the logic already present in WiFi Direct and WiFi Aware registration. This reduces inflated peer counts when the same device is discovered via multiple transports. Co-Authored-By: Claude Opus 4.5
WifiAwareManager.destroy() was never called in stopAllOperations(), causing IntentReceiverLeaked error when the service restarted. Co-Authored-By: Claude Opus 4.5
pendingMessageCount was never reset when notifications were suppressed due to the UI being visible. Messages accumulated silently while the user was in the feed, then fired as a stale batch (e.g. "56 messages received") after the user left the app and the next exchange occurred. Co-Authored-By: Claude Opus 4.5
Enable WiFi Aware peers to exchange messages over L3 data paths, reusing LAN TCP protocol. Adds transport parameter to LanTransport, network request/release to WifiAwareManager, and WiFi Aware exchange loop in RangzenService with deduplication against LAN peers. Co-Authored-By: Claude Opus 4.5
Adds `text` and `author_pseudonym`/`pseudonym` fields to message_composed, message_sent, message_received, and message_displayed telemetry payloads so researchers can read actual message content and trace authorship. Co-Authored-By: Claude Opus 4.5
Phase 1 - Foundation: - Identity contract: device_id_hash + exchange_id in all transport handshakes (BLE, LAN, WiFi Direct) for cross-transport peer correlation - Exchange pairing: initiator generates exchange_id, responder echoes it - Parameterized transport label in LegacyExchangeClient/Server - Unified exchange telemetry via ExchangeContext (removed duplicate trackExchangeWithLocation calls) - Per-message telemetry (trackMessageSent/Received) for LAN and WiFi Direct Phase 2 - Enrichment: - Added text_length, local_friend_count to message sent/received events - Added local_friend_count, location_age_ms to ExchangeContext payloads Phase 3 - New events: - message_displayed: tracked in feed adapter (deduplicated per session) - message_expired: tracked in cleanupByHearts with reason/age/priority - node_profile: periodic snapshot (message count, friends, hearts, age range) Phase 4 - Fresh GPS: - Added LocationHelper.requestFreshLocation() with 5s timeout - Exchange telemetry now requests fresh GPS fix before reporting Co-Authored-By: Claude Opus 4.5
Two changes to help diagnose and recover from BLE exchange failures between specific device pairs: 1. GATT diagnostics: track connection, MTU, service discovery, characteristic, CCCD, write, and notification stages during each BLE exchange. Included in both success and failure telemetry events so the server can pinpoint exactly where the GATT flow breaks. 2. Role-swap: after 3 consecutive exchange failures with a peer, swap the initiator/responder role. If this device was acting as GATT client and failing, it stops initiating and lets the peer try as client instead (and vice versa). Resets on success. Co-Authored-By: Claude Opus 4.5
cleanupByHearts() deleted messages that peers still had, causing them to be re-added as "new" on every exchange cycle (the "69 messages" notification loop). Fix by recording tombstones of deleted message IDs and rejecting re-received tombstoned messages. Also raise the 0-heart cleanup threshold from 3 to 5 days so it exceeds the propagation TTL. Co-Authored-By: Claude Opus 4.5
- Local message expiry based on hearts: 0=3d, 1=7d, 2+=14d - Enforce 3-day propagation TTL in getMessagesForExchange - Reject incoming messages older than 3 days in addMessage - Add message lifetime FAQ section (EN + FA) Co-Authored-By: Claude Opus 4.5
Replace hardcoded transport strings with centralized constants using the dashboard-expected values: bt-le, wifi-direct, wifi-aware, wlan. Bump version to v0.2.68. Co-Authored-By: Claude Opus 4.5
- Add CancellationException handlers to prevent crash during fragment destroy - Add isAdded checks before getString() calls in coroutines - Add safety guard in showError() function Fixes crash: "Fragment BlePairingFragment not attached to a context"
- Add combinedPriority() for exchange ordering (trust 50%, recency 25%, hearts 25%) - Fix heart count aggregation bug: merge hearts on existing messages - Add rate limiting: max 5 hearts per hour prevents gaming - Add telemetry for priority distribution monitoring - Low trust (<0.3) caps total priority regardless of hearts Co-Authored-By: Claude Opus 4.5
Adds device_model as top-level telemetry field.
Users now see the update dialog immediately when opening the app, not just when navigating to Settings. The check runs in onResume() so it triggers every time the app comes to foreground. Includes session tracking to avoid showing the dialog multiple times per app session. Co-Authored-By: Claude Opus 4.5
The Settings screen had a 2-second hardcoded delay that didn't wait for the actual APK download to complete. If download took longer, the update dialog would never show. Fixed by properly polling the UpdateClient state until ReadyToInstall or timeout (60s for large APKs on slow connections). Co-Authored-By: Claude Opus 4.5
Dashboard telemetry enhancements: - Location added to ALL telemetry events automatically - peer_snapshot periodic event (every 5 min) - App lifecycle events (start, foreground, background) - Error batching via custom Timber.Tree - Radio state in device telemetry Co-Authored-By: Claude Opus 4.5
- Add display_name field to TelemetryEvent for tester identification
- Include display_name in all telemetry events via TelemetryClient
- Use existing pseudonym from message compose (no extra UI needed)
- Generate fallback Tester-{hash} pseudonym if name not set
When users enter a pseudonym while composing messages, that name
automatically becomes their display_name in telemetry events.
Co-Authored-By: Claude Opus 4.5 ### Changes - Set otaWifiOnly=false in config.json - OTA downloads now work on cellular, not just WiFi ### Fixes - Phones on cellular were not receiving OTA updates - Update check worked but download was silently deferred - Users saw no indication that an update was available Co-Authored-By: Claude Opus 4.5
### Changes - Add 15+ new string resources for Feed and Compose pages - Add Farsi translations for all new strings - Update fragment_feed.xml: Liked, Hide mine, Recent, Restore swiped - Update fragment_compose.xml: Pseudonym hint, mesh network info - Update FeedFragment.kt: peers nearby, anonymous, time formatting - Update SettingsFragment.kt: statistics, radio status, update dialog - Update ComposeFragment.kt, BugReportActivity, SupportInboxActivity - All UI text now properly localized via string resources ### Fixes - Feed page no longer shows English on Farsi devices - Compose page mesh network explanation now in Farsi - Time formatting (Just now, X hours ago) now localized Co-Authored-By: Claude Opus 4.5
Co-Authored-By: Claude Opus 4.5
## Murmur 0.2.57 **SHA256:** **Size:** 9051502 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.56...v0.2.57
## Murmur 0.2.56 **SHA256:** **Size:** 9051106 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.55...v0.2.56
## Murmur 0.2.55 **SHA256:** **Size:** 9051022 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.54...v0.2.55
## Murmur 0.2.54 **SHA256:** **Size:** 9051038 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.53...v0.2.54
## Murmur 0.2.53 **SHA256:** **Size:** 9050778 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/commits/v0.2.53
## Murmur 0.2.52 **SHA256:** **Size:** 9050734 bytes ### Verification This APK was built and signed automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.51...v0.2.52
## Murmur 0.2.51 **SHA256:** **Size:** 10768123 bytes ### Verification This APK was built automatically from tag by GitHub Actions. **Full Changelog**: https://github.com/De-Novo-Group/rangzen-revival/compare/v0.2.50...v0.2.51
v0.2.49 - Support inbox for bug report messaging (fix install loop)
Add support inbox for bug report messaging - view messages from support, track submitted reports, send follow-up replies
Expand radio documentation with detailed tester instructions
Add radio connection documentation to FAQ and Testers Guide