Starting the APK-sharing hotspot failed intermittently with
"Failed to create hotspot: BUSY", sometimes for minutes, then
succeeded for no apparent reason. Two distinct causes, both
confirmed against a Pixel 9a via dumpsys and HAL logs.
1. Wi-Fi Aware holds the radio. The mesh's NAN interface and
Wi-Fi Direct's P2P interface cannot coexist on common chipsets:
HalDevMgr: bestIfaceCreationProposal is null, requestIface=P2P,
existingIface=[name=wlan0 type=STA, name=aware_nmi0 type=NAN]
WifiP2pNative: Failed to create P2p iface
The P2P state machine then stays in P2pDisabledState and answers
every createGroup with BUSY, while still broadcasting
WIFI_P2P_STATE_ENABLED. Whether sharing worked came down to
whether Aware happened to be attached, which is what made it look
random. WifiAwareController now releases Aware for the duration of
the hotspot and blocks restarts until it finishes.
2. Orphaned groups. A P2P group outlives the process that created
it, so a crash or swipe-away while hosting leaves one behind, and
the framework answers BUSY for as long as it exists. Startup now
removes a stale group first, but only one it can show is ours --
Wi-Fi Direct is shared with Cast, Android Auto and Quick Share.
Ownership is the group name we recorded creating, with the SSID
prefix as a fallback for orphans from older builds.
Also fixed while tracing these:
- Channel leak: initialize() ran on every retry attempt and the
channel was never closed, leaving a binder registration with
WifiP2pService per attempt. Observed climbing to 7 stale clients.
It is now initialised once and closed after removeGroup replies.
- BUSY is the framework's catch-all reply, so retrying was futile
for permanent causes and too impatient for real contention.
Retries now back off 1s/2s/4s/8s and only for genuinely transient
failures; P2P being off fails immediately with a message that says
so rather than 15 seconds ending in "busy".
- Turning Wi-Fi off mid-session left the UI showing an active
hotspot forever; it now aborts cleanly.
Retry and startup decisions are extracted into HotspotStartupPolicy,
which has no Android dependencies and is covered by 13 unit tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hebrew, Polish, Simplified Chinese, Traditional Chinese, Malay, Tamil,
and Ukrainian were only 10-12% translated (38-48 of 395 string keys),
falling back to English for nearly everything. Backfill each to 100%
(395/395 keys), including the two shared plurals (notification_and_more,
people_count) with locale-correct CLDR plural categories.
Also:
- Fix values-zh-rTW: ~27 keys in the verify_*/fingerprint_* block were
Simplified Chinese pasted into the Traditional Chinese file (e.g. 验证
instead of 驗證); corrected to proper Traditional Chinese script.
- Fix a leftover English verify_*/fingerprint_* block (~30 keys) present
in pl, ms, ta, uk that predated this change and wasn't part of the
originally-missing-key set.
- Fix values-pl version_prefix, mistranslated as "w%1$s" instead of
preserving the literal version-string prefix "v%1$s".
- Fix a duplicate-key bug in values-ms where a stale untranslated
<string name="notification_and_more"> coexisted with the new
<plurals name="notification_and_more">.
All translations are machine-translated (flagged as such via an
in-file comment: "pending native-speaker review") and should be
reviewed by fluent speakers before being considered final. Verified:
well-formed XML, exactly 395/395 keys with no duplicates in all 7
files, zero placeholder (%1$s/%2$d/etc.) mismatches against the
English source, and a clean `./gradlew :app:processDebugResources`
resource compile.
Relates to #737 (multilingual support request) — this addresses the
"languages already added but barely translated" half of that issue;
an in-app language switcher / android:localeConfig and a proper
community-translation pipeline (e.g. Weblate) are separate follow-ups.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Receivers hard-cap reassembly at MAX_FRAGMENTS_PER_ID (256), but the
generic send path fragmented packets with no caller cap (0xFFFF), so
broadcast file transfers above ~120 KB were fully transmitted yet
undeliverable. FragmentingPacketSender now caps fragmentation at
MAX_FRAGMENTS_PER_ID and reports failure via a new
TransferProgressEvent.failed flag, which surfaces as
DeliveryStatus.Failed in the UI and as a file_send error in the debug
test hook instead of an indefinite wait.
Adds FragmentingPacketSenderTest and a file_oversize mesh-lab scenario
asserting sender-side rejection.
Debug-only broadcast receiver (app/src/debug) exposes mesh operations over
ADB: scan, connect, Noise handshake, DMs, broadcast, announce, file
send/receive with SHA-256 verification, BLE toggle, state dumps, and raw
packet injection. tools/release_gate/mesh_lab.py orchestrates scenarios
(dm, broadcast, file, file_private, raw) on two live devices and emits
evidence JSON.
Align the background location footer with the battery optimization layout so primary, check-again, and skip stay at the same height when navigating between them.
Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses review feedback: the size-cap error was posted to the main
mesh timeline, so a user sending from a private chat or channel never
saw it. rejectIfOversized now posts to the private conversation
(addPrivateMessageNoUnread) or channel (addChannelMessage) the send
originated from, falling back to the main timeline for public sends.
Adds regression tests for both routings.
- MediaSendingManager: surface a chat system message when a picked file
exceeds the ~10 MiB send cap instead of silently dropping the send
(voice/image/file paths), completing the 'user-visible failure'
requirement of the rollout gate
- BinaryProtocol: catch Exception instead of Throwable in decodeCore so
OutOfMemoryError is never swallowed and masked as a parse failure
- docs/file_transfer.md: mark the compressed-expansion rollout gate
resolved; support for legacy >10 MiB compressed transfers is
explicitly ended
Extract PeerColorStyle so each palette owns saturation/value, keeping
hues stable while dark mode stays bright-but-muted and light mode avoids
neon labels. New themes only need to supply their own style.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep disable at the sheet footer so enabling is next to nearby channels while turning location off stays a secondary action.
Co-authored-by: Cursor <cursoragent@cursor.com>
- header star wobbles and its outline turns orange when the peer
favorites you; it only fills once you favorite them back
- mirror the 'favorited you' system notice into the private
conversation (mesh path), matching the main chat
- expose reactive peerFavoritedUs state driven by
FavoritesPersistenceService; keep system notices silent
(no unread badge, read receipt or push)
Show ic_spec_lock_open while idle or handshaking, then Crossfade to the
closed lock on success or failure — timed with the existing tint wash so
the shackle settling reads as one smooth transition.
Co-authored-by: Cursor <cursoragent@cursor.com>