Repository navigation
Conversation
Addresses three issues from Codex review on permissionlesstech#808. The Wi-Fi Aware hold could be defeated by a race. startIfPossible() does a long stretch of async work between checking the hold and assigning the service, so holdForHotspot() landing in that window left an in-flight start free to resurrect NAN behind the hotspot's back, putting every P2P attempt back on BUSY. The hold is now rechecked before committing the service, and the freshly started service is torn down if the hotspot claimed the radio meanwhile. Startup error paths bypassed cleanup. A web-server failure, a null connection info, or a throw from the outer block stopped the manager but left the Aware hold set, blocking all mesh starts until the user happened to retry or close the screen. Worse, an error after the server had started left it serving the APK on port 9999 -- including after the device reconnected to an ordinary Wi-Fi network -- because only stopHotspot() cleared it. Both follow from the same gap: cleanup lived at the call sites rather than in one place. All failures now go through failWith(), which shares teardown() with stopHotspot() and releases the server, the manager and the hold together. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arget (leans toward finger, blushes red with proximity, snaps on hover, REJECT haptic on cancel, firm click on enter/exit), release-anywhere tracking replaces button-owned release, timestamp left of waveform showing elapsed only, muted 1.5dp red outline, neutral grey recording pill with depth
…ain recording visualizer row to the text field's 22dp content height so the separator no longer shifts
The previous recheck narrowed the race without closing it. Testing
hotspotHold outside lifecycleLock left this interleaving:
1. the recheck passes, hold not yet set
2. holdForHotspot() sets the flag and calls stop(), which takes the
lock, finds nothing published, and returns having stopped nothing
3. this block takes the lock and publishes the service anyway
Aware ends up running while the hotspot believes it owns the radio,
which is the original failure.
The hold is now tested inside the same synchronized block that
publishes, so the check and the publication are one step against
stop(). Ordering holds because holdForHotspot() sets the flag before
calling stop(): either the publisher observes the flag and abandons,
or stop() observes the published service and tears it down. There is
no interleaving that leaves a service published with the hold set.
stopServices() stays outside the lock since it can block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wifi-direct-reliability fix: make Wi-Fi Direct hotspot sharing reliable
…omposed shouldCancel state could be one frame stale on a fast slide-and-lift (PR review)
…ex/persistent-conversations Persist private conversations in People
…ex/wear-notification-stream-fix Fix Wear OS DM notification delivery
…ce-recording-ux Voice recording UX: slide-to-cancel, magnetic target, refined recording pill
…ex/reproducible-builds Enable reproducible Android builds and releases
A bare `test -n` does not abort in a shell without set -e, so a failed mktemp would let `--out ""` resolve to the repository root and write private evidence there. Guard with an explicit exit instead.
…ex/mesh-lab-agent-skill Add Mesh Lab agent skill
…ex/polish-persistent-conversations-v2 Polish persistent private conversations
…ex/android-ui-visual-review-skill
…ex/bump-version-2.0.2 Bump Android version to 2.0.2
…ex/wear-ui-shape-accessibility Improve Wear round-screen layouts and chat scrolling
…ex/release-wear-0.1.3 Bump Wear OS version to 0.1.3
…reconnect-recovery fix(nostr): keep retrying relays instead of giving up on them forever
…-lookback fix(nostr): cap DM envelope backdating at 24 hours
…ation Use full-payload packet IDs for duplicate detection and simplify the explanatory comment.
…total-checks-race fix: synchronize totalChecks increment in NostrEventDeduplicator
…r-ongoing-activity Add Wear ongoing activity for the mesh service
Replace the white-background pixel bubble with the radar bubble: a green chat bubble with two broadcast rings on a near-black ground. The rings open toward the tail (bottom-right, like BitChatIcon) and all shapes stay inside the 66dp adaptive-icon safe zone. The notification small icon and ongoing-activity static icon now use the same glyph instead of a stock chat bubble, and the monochrome layer matches the launcher.
…ude/wearos-logo-redesign-20af78 Redesign Wear OS launcher and notification icons
…ease-wear-0.1.4 Bump Wear release to 0.1.4
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d66415ad3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "https://github.com/permissionlesstech/bitchat-android/releases/latest/" + | ||
| "download/bitchat-android-universal.apk", |
There was a problem hiding this comment.
Point the APK source at a Cirabit release
When a user without a locally shareable APK selects the new Prepare/Download action, this is the only configured source. It serves the upstream BitChat APK, whose package is not this app's com.cirabit.android; UniversalApkManager.downloadedVersionName() rejects package mismatches, and release builds will also reject its different signer. Consequently every default download fails instead of producing a shareable Cirabit APK. Configure this source (and the matching release-metadata client) to Cirabit's own release assets.
Useful? React with 👍 / 👎.
| # releases. Upstream bitchat ships its own fingerprint here; Cirabit releases are | ||
| # signed with a different key, so leave this empty until the Cirabit release | ||
| # certificate fingerprint is filled in (TODO: set to cirabit-release.jks SHA-256). | ||
| BITCHAT_GITHUB_RELEASE_CERT_SHA256= |
There was a problem hiding this comment.
Pin the Cirabit release certificate
For users installed through Play or another signing lineage, a legitimate Cirabit GitHub universal APK will not match ownCerts; with this value blank, BuildConfig.GITHUB_RELEASE_CERT_SHA256 contributes no trusted release signer and verifyApkSignature() rejects the download. This blocks the new prepare-for-sharing/update path precisely for the separate-release-key case it is intended to support. Ship the actual Cirabit GitHub release certificate fingerprint rather than an empty default.
Useful? React with 👍 / 👎.
| val msg = CirabitMessage( | ||
| id = stableMessageID, | ||
| id = UUID.randomUUID().toString().uppercase(), |
There was a problem hiding this comment.
Align private-media echo IDs with recipient ACKs
For every encrypted private file, the sender persists a random UUID here, but the receiver computes FileMessageIdUtil from the packet timestamp and encoded file payload and returns that deterministic value in its delivery ACK. The ACK is then used directly to update the sender's message status, so it cannot match this local echo and receiver confirmation is discarded. Persist the same deterministic ID (or carry one on the wire) for the sender echo.
Useful? React with 👍 / 👎.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…pin Robolectric to SDK 35
Sync with upstream permissionlesstech/bitchat-android
main.Upstream range
9fb8d4b..6ade682(2026-03-15 → 2026-10-11): 345 non-merge commits, up tov2.0.2-wear.0.1.4. Highlights: Wi‑Fi Aware transport +MeshService/UnifiedMeshServicefacade, NoisePEER_STATEcapabilities and the private-media preparation pipeline, live push‑to‑talk, a Wear OS module, UI redesign (palette, conversation headers, bubbles), fragment/TLV hardening, reproducible builds, dependency locking and verification.Method
A direct merge gave 290 false conflicts because of the
com.bitchat→com.cirabitrename. So I applied Cirabit's rename (paths, packages,Bitchat*→Cirabit*,bitchat1:→cirabit1:) to both the merge base and upstream, then ran a 3‑way merge. That left 56 real conflicts. The commit is a true merge, with parentsmain+upstream/main.Conflicted files and how they were resolved
android-build.yml,release.yml,AGENTS.md,README.md,CHANGELOG.md,GOOGLE_PLAY.md,fetch-georelays.yml): kept Cirabit's version (its own signing/release flow and branding). Upstream's reproducible-build workflows were not adopted..gitignore: union of both.nostr_relays.csv: upstream (generated data).docs/file_transfer.md,tools/arti-build/README.md: upstream.gradle/libs.versions.toml, wrapper: took the newer upstream versions (AGP 9.3.1, Kotlin 2.4.10, compileSdk 37, Gradle 9.6.1) and kept Cirabit-only entries (biometric, coroutines-test, lifecycle-process). Re-added theandroidx-security-cryptoalias because wear uses it. Robolectric went back to 4.15, following upstream's note.app/build.gradle.kts: kept Cirabit's applicationId, versionName 1.7.3, signing and JVM 17. Added upstream's buildConfig cert field and composeCompiler block. EnabledincludeAndroidResourcesfor unit tests.build.gradle.kts: Cirabit's CVE pins plus upstream dependency locking.locales_config.xml: kept Cirabit's (en-US, pt-BR).strings.xml(en, pt-BR): kept Cirabit's strings and added the upstream strings that were missing. Addedperm_type_wifi_aware.NoiseEncrypted.kt: upstreamPEER_STATEtakes 0x21. CirabitREACTIONmoved 0x21 → 0x22.BinaryProtocol.kt:MSG_REACTION0x23 and upstreamVOICE_FRAME0x29 both kept. Kept Cirabit's per-type payload limits; empty expanded payloads are now rejected (as upstream does).AppConstants.kt,FragmentManager.kt: kept Cirabit's limits (65 535 fragments, 50 MB files) and its parametrized FragmentManager. Took upstream'screateFragments(maxFragments). Added the upstreamRouterconstants.CirabitFilePacket.kt: Cirabit's multi-chunk content plus upstream's skipping of unknown TLVs.SecureIdentityStateManager.kt,SecureKeyValueStore.kt: kept Cirabit's Tink store. Added an atomicEditor/commit()and a test-only plaintext constructor so the upstream peer-state/pin logic works on top of it.BluetoothMeshService.kt,MeshDelegate.kt,MeshService.kt,UnifiedMeshService.kt,PacketProcessor.kt,MessageHandler.kt,SecurityManager.kt: took upstream's architecture and wired Cirabit reactions and stable (timestamp-pinned) IDs into it. Added default-arg overloads forsendMessage/sendFileBroadcastwith a timestamp. Reactions over MeshCore/Wi‑Fi Aware are a no-op for now.BluetoothPacketBroadcaster.kt: upstream GATT send queue.GeohashRepository.kt,LocationChannelManager.kt,SystemLocationProvider.kt,FileUtils.kt,PermissionManager.kt,OrientationAwareActivity.kt: upstream logic plus Cirabit additions (string-resource labels, Wi‑Fi Aware label).MainActivity.kt: kept Cirabit's app lock and update check, and integrated upstream's Wi‑Fi Aware delegate wiring, BLE-optional check and best-effort foreground-service start.ChatViewModel.kt,ChatScreen.kt,MessageComponents.kt,MeshPeerListSheet.kt,MediaSendingManager.kt,CommandProcessor.kt,AboutSheet.kt,ChatHeader.kt,ChatUserSheet.kt,GeohashViewModel.kt,SecurityVerificationSheet.kt,MeshDelegateHandler.kt,FullScreenImageViewer.kt,ImageMessageItem.kt,MeshForegroundService.kt: took the upstream UI redesign and kept Cirabit's features: reactions, app lock and update toggles, localized commands with/pay, stable public file IDs, theSecureImageDecoderbounded decode, and the non-deprecatedstopForegroundcalls.MessageHandlerTesttoStableFileIdMessageHandlerTest. RemovedNotificationManagerTest(upstream removed the class it tests) and the upstream "fragment set > 256 rejected" test (it contradicts Cirabit's limit). Oversized-file tests now useMAX_FILE_SIZE_BYTES.com/cirabit/watchand fixed the deprecatedsrcDircalls.Build / tests
./gradlew :app:assembleDebug: succeeded locally on an earlier pass.:app:testDebugUnitTest: the earlier run had 633 tests, 13 failed. I made some fixes afterwards, but the final local test run was cut short to save time. CI needs to verify this branch. Lockfiles were regenerated with--write-locks.gradle/verification-metadata.xmlwas not updated, so strict dependency verification may fail until it is regenerated.:wearwas not built (it needs JDK 21).Needs human review
BITCHAT_GITHUB_RELEASE_CERT_SHA256is blanked ingradle.properties. Fill in the Cirabit release cert fingerprint.verification-metadata.xmlneeds regenerating.