Murmur

Anonymous mesh messaging without Internet

★ LATEST

Murmur v0.2.96

Build 96 • 8.9 MB • Android 8.0+ (API 26) • Released Jan 29, 2026 21:54 UTC (13:54 PST)

Changelog

## 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

Previous Versions

v0.2.95 (build 95)

8.9 MB • Android 8.0+ • Jan 28, 2026 23:52 UTC (15:52 PST)
## 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

v0.2.94 (build 94)

8.8 MB • Android 8.0+ • Jan 28, 2026 21:19 UTC (13:19 PST)
Includes the critical fix: use publicIdPrefix as peer ID for symmetric BLE initiator selection.

Co-Authored-By: Claude Opus 4.5 

v0.2.93 (build 93)

8.8 MB • Android 8.0+ • Jan 28, 2026 20:47 UTC (12:47 PST)
Release 0.2.93

v0.2.92 (build 92)

8.8 MB • Android 8.0+ • Jan 28, 2026 20:41 UTC (12:41 PST)
Release 0.2.92

v0.2.91 (build 91)

8.8 MB • Android 8.0+ • Jan 28, 2026 20:28 UTC (12:28 PST)
Co-Authored-By: Claude Opus 4.5 

v0.2.90 (build 90)

8.8 MB • Android 8.0+ • Jan 28, 2026 20:13 UTC (12:13 PST)
Co-Authored-By: Claude Opus 4.5 

v0.2.89 (build 89)

8.8 MB • Android 8.0+ • Jan 28, 2026 19:48 UTC (11:48 PST)
Co-Authored-By: Claude Opus 4.5 

v0.2.88 (build 88)

8.8 MB • Android 8.0+ • Jan 28, 2026 19:24 UTC (11:24 PST)
Co-Authored-By: Claude Opus 4.5 

v0.2.87 (build 87)

8.8 MB • Android 8.0+ • Jan 28, 2026 18:11 UTC (10:11 PST)
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 

v0.2.86 (build 86)

8.8 MB • Android 8.0+ • Jan 28, 2026 17:56 UTC (09:56 PST)
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 

v0.2.85 (build 85)

8.8 MB • Android 8.0+ • Jan 28, 2026 17:37 UTC (09:37 PST)
- 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 

v0.2.84 (build 84)

8.8 MB • Android 8.0+ • Jan 28, 2026 10:04 UTC (02:04 PST)
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 

v0.2.83 (build 83)

8.8 MB • Android 8.0+ • Jan 28, 2026 09:45 UTC (01:45 PST)
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 

v0.2.82 (build 82)

8.8 MB • Android 8.0+ • Jan 28, 2026 09:33 UTC (01:33 PST)
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 

v0.2.81 (build 81)

8.8 MB • Android 8.0+ • Jan 28, 2026 09:23 UTC (01:23 PST)
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 

v0.2.80 (build 80)

8.8 MB • Android 8.0+ • Jan 28, 2026 08:59 UTC (00:59 PST)
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 

v0.2.79 (build 79)

8.8 MB • Android 8.0+ • Jan 28, 2026 08:41 UTC (00:41 PST)
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 

v0.2.78 (build 78)

8.8 MB • Android 8.0+ • Jan 28, 2026 08:17 UTC (00:17 PST)
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 

v0.2.77 (build 77)

8.7 MB • Android 8.0+ • Jan 28, 2026 08:04 UTC (00:04 PST)
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 

v0.2.76 (build 76)

8.7 MB • Android 8.0+ • Jan 28, 2026 07:45 UTC (23:45 PST)
WifiAwareManager.destroy() was never called in stopAllOperations(),
causing IntentReceiverLeaked error when the service restarted.

Co-Authored-By: Claude Opus 4.5 

v0.2.75 (build 75)

8.7 MB • Android 8.0+ • Jan 28, 2026 07:40 UTC (23:40 PST)
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 

v0.2.74 (build 74)

8.7 MB • Android 8.0+ • Jan 28, 2026 07:19 UTC (23:19 PST)
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 

v0.2.73 (build 73)

8.7 MB • Android 8.0+ • Jan 28, 2026 06:06 UTC (22:06 PST)
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 

v0.2.72 (build 72)

8.7 MB • Android 8.0+ • Jan 28, 2026 04:48 UTC (20:48 PST)
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 

v0.2.71 (build 71)

8.7 MB • Android 8.0+ • Jan 28, 2026 03:59 UTC (19:59 PST)
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 

v0.2.70 (build 70)

8.7 MB • Android 8.0+ • Jan 28, 2026 03:10 UTC (19:10 PST)
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 

v0.2.69 (build 69)

8.7 MB • Android 8.0+ • Jan 27, 2026 23:20 UTC (15:20 PST)
- 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 

v0.2.68 (build 68)

8.7 MB • Android 8.0+ • Jan 27, 2026 19:46 UTC (11:46 PST)
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 

v0.2.67 (build 67)

8.7 MB • Android 8.0+ • Jan 27, 2026 15:28 UTC (07:28 PST)
- 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"

v0.2.66 (build 66)

8.7 MB • Android 8.0+ • Jan 27, 2026 04:13 UTC (20:13 PST)
- 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 

v0.2.65 (build 65)

8.7 MB • Android 8.0+ • Jan 27, 2026 02:51 UTC (18:51 PST)
Adds device_model as top-level telemetry field.

v0.2.64 (build 64)

8.7 MB • Android 8.0+ • Jan 27, 2026 00:46 UTC (16:46 PST)
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 

v0.2.63 (build 63)

8.7 MB • Android 8.0+ • Jan 27, 2026 00:40 UTC (16:40 PST)
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 

v0.2.62 (build 62)

8.7 MB • Android 8.0+ • Jan 27, 2026 00:21 UTC (16:21 PST)
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 

v0.2.61 (build 61)

8.7 MB • Android 8.0+ • Jan 26, 2026 23:30 UTC (15:30 PST)
- 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 

v0.2.60 (build 60)

8.7 MB • Android 8.0+ • Jan 26, 2026 23:10 UTC (15:10 PST)
### 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 

v0.2.59 (build 59)

8.7 MB • Android 8.0+ • Jan 26, 2026 22:14 UTC (14:14 PST)
### 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 

v0.2.58 (build 58)

8.7 MB • Android 8.0+ • Jan 26, 2026 21:03 UTC (13:03 PST)
Co-Authored-By: Claude Opus 4.5 

v0.2.57 (build 57)

8.6 MB • Android 8.0+ • Jan 26, 2026 16:23 UTC (08:23 PST)
## 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

v0.2.56 (build 56)

8.6 MB • Android 8.0+ • Jan 26, 2026 09:29 UTC (01:29 PST)
## 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

v0.2.55 (build 55)

8.6 MB • Android 8.0+ • Jan 26, 2026 09:17 UTC (01:17 PST)
## 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

v0.2.54 (build 54)

8.6 MB • Android 8.0+ • Jan 26, 2026 09:12 UTC (01:12 PST)
## 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

v0.2.53 (build 53)

8.6 MB • Android 8.0+ • Jan 26, 2026 08:23 UTC (00:23 PST)
## 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

v0.2.52 (build 52)

8.6 MB • Android 8.0+ • Jan 26, 2026 07:55 UTC (23:55 PST)
## 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

v0.2.51 (build 51)

10.3 MB • Android 8.0+ • Jan 26, 2026 06:28 UTC (22:28 PST)
## 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 (build 49)

10.3 MB • Android 8.0+ • Jan 26, 2026 05:14 UTC (21:14 PST)
v0.2.49 - Support inbox for bug report messaging (fix install loop)

v0.2.48 (build 48)

10.3 MB • Android 8.0+ • Jan 26, 2026 04:40 UTC (20:40 PST)
Add support inbox for bug report messaging - view messages from support, track submitted reports, send follow-up replies

v0.2.42 (build 47)

8.4 MB • Android 8.0+ • Jan 26, 2026 03:38 UTC (19:38 PST)
Expand radio documentation with detailed tester instructions

v0.2.41 (build 46)

8.4 MB • Android 8.0+ • Jan 26, 2026 03:24 UTC (19:24 PST)
Add radio connection documentation to FAQ and Testers Guide