Commit Graph

25 Commits

Author SHA1 Message Date
Siavash Sameni
5799d18aee debug: add tracing to nativeSignalConnect entry
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 3m46s
2026-04-09 10:17:13 +04:00
Siavash Sameni
46c9ee1be3 fix: single thread for entire signal lifecycle — runtime never dropped (libcrypto TLS fix)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 37s
Build Release Binaries / build-amd64 (push) Failing after 3m52s
2026-04-09 10:11:33 +04:00
Siavash Sameni
b53eae9192 fix: split start() into connect+register (inline) + run() (separate thread) — avoids thread::spawn closure stack overflow
Some checks failed
Mirror to GitHub / mirror (push) Failing after 35s
Build Release Binaries / build-amd64 (push) Failing after 3m26s
2026-04-09 10:02:07 +04:00
Siavash Sameni
76e9fe5e43 fix: single thread+runtime for signal lifecycle — avoids ring/libcrypto TLS conflict on pthread_exit
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 3m46s
2026-04-09 09:44:46 +04:00
Siavash Sameni
abc96e8887 refactor: separate SignalManager from WzpEngine for direct calling
Some checks failed
Mirror to GitHub / mirror (push) Failing after 40s
Build Release Binaries / build-amd64 (push) Failing after 3m40s
SignalManager (NEW):
- Dedicated Rust struct with its own QUIC connection to _signal
- Separate JNI handle (nativeSignalConnect/GetState/PlaceCall/etc)
- Kotlin wrapper polls state every 500ms via getState() JSON
- Lives independently of WzpEngine — survives across calls
- connect() blocks briefly on 8MB thread, then recv loop runs on dedicated thread

WzpEngine (CLEANED):
- Back to pure media-only role (audio, codec, FEC, jitter)
- Removed start_signaling/place_call/answer_call methods
- Removed signal_transport/signal_fingerprint from EngineState

CallViewModel:
- Two separate managers: signalManager (persistent) + engine (per-call)
- Two separate polling loops: signalPollJob + statsJob
- Auto-connect to media room when signal polling detects "setup" state
- hangupDirectCall() ends media but keeps signal alive

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:34:36 +04:00
Siavash Sameni
3a6ae61f8d fix: show real identity fingerprint (SHA-256 full format) on Android home screen
Some checks failed
Mirror to GitHub / mirror (push) Failing after 39s
Build Release Binaries / build-amd64 (push) Failing after 1m30s
2026-04-09 09:12:47 +04:00
Siavash Sameni
4c536d256b fix: install rustls crypto provider once in nativeInit, not per-thread (libcrypto TLS conflict)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 4m18s
2026-04-09 09:07:40 +04:00
Siavash Sameni
ed09c2e8cc fix: use block_on pattern for signaling (same as start_call) — no thread::spawn
Some checks failed
Mirror to GitHub / mirror (push) Failing after 37s
Build Release Binaries / build-amd64 (push) Failing after 3m50s
2026-04-09 08:33:08 +04:00
Siavash Sameni
f44306cc17 fix: move ALL signaling code into JNI-spawned 8MB thread — zero Rust on caller stack
Some checks failed
Mirror to GitHub / mirror (push) Failing after 40s
Build Release Binaries / build-amd64 (push) Failing after 3m51s
2026-04-09 08:19:48 +04:00
Siavash Sameni
faec332a8c fix: remove panic::catch_unwind from nativeStartSignaling — stack overflow on Android
Some checks failed
Mirror to GitHub / mirror (push) Failing after 42s
Build Release Binaries / build-amd64 (push) Failing after 3m28s
2026-04-09 08:04:47 +04:00
Siavash Sameni
5d8e743cbf feat: Android engine + Kotlin API for direct 1:1 calling
Some checks failed
Mirror to GitHub / mirror (push) Failing after 35s
Build Release Binaries / build-amd64 (push) Failing after 3m47s
Rust engine:
- start_signaling(): persistent _signal connection, presence registration
- Signal recv loop: handles DirectCallOffer, CallRinging, CallSetup, Hangup
- New CallState variants: Registered, Ringing, IncomingCall
- Stats expose incoming_call_id, incoming_caller_fp, incoming_caller_alias, sas_code
- New EngineCommands: PlaceCall, AnswerCall, RejectCall

JNI bridge:
- nativeStartSignaling(relay, seed, token, alias)
- nativePlaceCall(targetFp)
- nativeAnswerCall(callId, mode)

Kotlin API (WzpEngine.kt):
- startSignaling(relay, seed, token, alias)
- placeCall(targetFingerprint)
- answerCall(callId, mode) — 0=Reject, 1=AcceptTrusted, 2=AcceptGeneric

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 06:02:48 +04:00
Siavash Sameni
d06cf66538 fix: auto codec, force-ping button, relay delete button
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 1m57s
1. Auto codec: new "Auto" position on quality slider (JNI index 7).
   When selected, the engine uses the relay's chosen_profile from
   CallAnswer instead of the local preference. Slider now has 8
   positions: Studio 64k → Auto → Codec2 1.2k.

2. Force ping: added refresh button (↻) in Manage Relays dialog
   header. Calls pingAllServers() to re-check all relays on demand.

3. Delete relay fix: the X button was inside a Surface(onClick=...)
   which swallowed the touch event. Replaced with a separate Surface
   that properly intercepts the click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:22:24 +04:00
Siavash Sameni
53f8bf8fff feat: full quality tiers + slider UI + key-change warning on Android
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 1m52s
1. Wire protocol: add Opus 32k/48k/64k (CodecId 6/7/8) + STUDIO
   profiles with is_opus() helper. Opus enc/dec accept all Opus variants.

2. JNI bridge: expand profile_from_int to 7 levels (0-6) mapping to
   GOOD, DEGRADED, CATASTROPHIC, Codec2_3200, STUDIO_32K/48K/64K.

3. Settings UI: replace radio buttons with Material3 Slider — 7 stops
   from Studio 64k (green) to Codec2 1.2k (dark red), matching desktop.

4. Key-change warning: AlertDialog on connect when server fingerprint
   has changed. Shows old vs new fingerprint, Accept New Key or Cancel.
   Accepting saves the new fingerprint and proceeds with the call.

5. Engine recv: handle studio codec IDs in auto-switch path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:11:29 +04:00
Siavash Sameni
a8dc350a65 feat: codec selection in settings (Opus / Opus Low / Codec2)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 41s
Build Release Binaries / build-amd64 (push) Failing after 3m41s
- Settings UI: radio buttons for encode codec selection
- Persisted via SettingsRepository
- Passed through WzpEngine.startCall(profile=) → JNI → Rust CallStartConfig
- Decode always accepts all codecs (per-packet codec_id switch)
- 0 = Opus 24k (GOOD), 1 = Opus 6k (DEGRADED), 2 = Codec2 1.2k (CATASTROPHIC)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:50:01 +04:00
Siavash Sameni
18f7faa279 fix: ping as engine instance method — same lifecycle as call
Some checks failed
Mirror to GitHub / mirror (push) Failing after 7s
Build Release Binaries / build-amd64 (push) Failing after 19s
Ping was a static JNI method that loaded the .so before nativeInit,
crashing jemalloc. Now ping is an instance method on WzpEngine:

- Engine is created once (nativeInit), reused for both ping and call
- pingRelay() uses same tokio runtime pattern as startCall()
- Auto-pings all servers on app launch (after engine init)
- No process restart needed
- TOFU fingerprints saved on first successful ping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:49:33 +04:00
Siavash Sameni
264ef9c4d4 feat: relay ping with RTT, server TOFU, lock icons (Phase 2 backport)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 40s
Build Release Binaries / build-amd64 (push) Failing after 3m48s
Rust JNI:
- nativePingRelay: QUIC connect with 3s timeout, returns RTT + server
  certificate fingerprint as JSON. Static method, no engine needed.

Kotlin:
- WzpEngine.pingRelay() static wrapper
- SettingsRepository: TOFU fingerprint persistence (tofu_{address} keys)
- CallViewModel: pingAllServers() coroutine, lockStatus() helper,
  PingResult/LockStatus data types
- InCallScreen: server chips show lock icon + RTT color (green/yellow),
  "Ping All" button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:43:53 +04:00
Siavash Sameni
5e93cb74f2 fix: filter tracing to INFO for wzp crates, WARN for jni crate
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 4m7s
The jni crate emits VERBOSE logs for every JNI method lookup (~10 lines
per call, 100+ calls/sec on audio threads). This floods logcat, consumes
CPU, and triggers system kills. Filter to only show INFO+ for our crates
and WARN+ for everything else.

Also fix build script: clean full Rust target to ensure libc++_shared.so
is always copied by cargo-ndk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 21:37:29 +04:00
Siavash Sameni
9eed94850d fix: DirectByteBuffer audio path — eliminate JNI array copies
Some checks failed
Build Release Binaries / build-amd64 (push) Failing after 3m43s
Adds nativeWriteAudioDirect / nativeReadAudioDirect JNI functions
that accept a DirectByteBuffer instead of ShortArray. The buffer's
native memory is accessed directly by Rust via pointer — no
GetShortArrayRegion / SetShortArrayRegion, no GC-managed array
copies on the audio hot path.

This fixes SIGBUS crashes on Android 16 where ART's concurrent
mark-compact GC crashes when flipping thread roots during JNI
array operations on MAX_PRIORITY audio threads.

Old ShortArray methods kept for backward compatibility.
AudioPipeline switched to use Direct variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:29:08 +04:00
Siavash Sameni
33fab9a049 fix: vec allocation for AudioRing, catch_unwind on tracing init, profiling
Some checks failed
Build Release Binaries / build-amd64 (push) Failing after 3m49s
- AudioRing: use vec![].into_boxed_slice() instead of Box::new([]) to
  avoid 32KB stack allocation that crashes scudo on Android
- JNI bridge: wrap tracing_subscriber init in catch_unwind to survive
  sharded_slab allocation failures on some devices
- Engine: per-step encode profiling (avg_agc_us, avg_opus_us, avg_fec_us,
  avg_send_us) logged every 5 seconds in send stats

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:41:46 +04:00
Claude
2b3bdae440 fix: enable Rust tracing → Android logcat via tracing-android
Rust tracing subscriber was never initialized — all info!/warn!/error!
calls in the engine went to /dev/null. This meant our send/recv health
logging was invisible and we couldn't confirm the congestion fix was
active.

Now initializes tracing-android layer on first nativeInit(), routing
all Rust logs to logcat under tag "wzp_android". Also expanded logcat
filter in DebugReporter to capture engine-level log lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:03:28 +00:00
Claude
0835c36d0f feat: settings page with persistence, client alias in handshake, fix null fingerprints
Some checks failed
Build Release Binaries / build-amd64 (push) Failing after 3m34s
- Add SettingsScreen with identity (alias, key backup/restore), audio defaults,
  server management, network prefs, and default room
- SettingsRepository persists all settings via SharedPreferences
- Auto-generate random display names on first launch (e.g. "Swift Wolf")
- Thread alias through CallOffer → relay handshake → RoomUpdate broadcast
- Derive caller fingerprint from identity key in relay handshake (fixes null
  fingerprints when --auth-url is not set)
- Persist identity seed for stable fingerprints across reconnects
- Add alias field to SignalMessage::CallOffer (serde default for backward compat)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 03:56:33 +00:00
Claude
bf91cf25bd feat: add real audio pipeline with Opus + RaptorQ FEC
- AudioPipeline: Kotlin AudioRecord/AudioTrack on JVM threads, PCM
  shuttled to Rust via lock-free ring buffers + JNI
- FEC: RaptorQ fountain codes on encode (5 frames/block, 20% repair
  ratio for GOOD profile), decoder feeds repair symbols for recovery
- Real audio level meter from mic RMS (replaces fake animation)
- Room name editable in UI (default: "android")
- Relay changed to pangolin.manko.yoga:4433
- Stats overlay shows FEC recovered count
- CallState now synced from polled stats (fixes "Connecting" stuck bug)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:33:59 +00:00
Claude
8d5f6fe044 feat: wire QUIC transport, JNI bridge, connect UI + add docs
- Replace raw FFI with proper `jni` crate for string marshalling
- Wire QUIC transport in engine: connect to relay, crypto handshake
  (CallOffer/CallAnswer, X25519+Ed25519), send/recv MediaPackets
- Feed received packets into jitter buffer (was previously ignored)
- Add connect screen UI with CALL button (idle state) and in-call
  controls (mute, speaker, hang up, live stats)
- Hardcode relay 172.16.81.125:4433, room "android"
- Add comprehensive docs in docs/android/:
  architecture.md (8 mermaid diagrams), build-guide.md,
  debugging.md, maintenance.md, roadmap.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 04:43:49 +00:00
Claude
780309fede fix: crash on launch — don't auto-start call, handle null JNI strings, remove stdout tracing
- CallActivity no longer auto-starts a call on launch
- CallViewModel lazily inits engine only when startCall() is called
- nativeGetStats nullable return handled safely in Kotlin
- Removed tracing_subscriber::fmt() which panics on Android (no stdout)
- All JNI calls wrapped in try/catch on Kotlin side

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 02:04:23 +00:00
Claude
e7b1c3372a feat: Android VoIP client — Phase 2 (JNI bridge, Compose UI, AEC pipeline wiring)
- JNI bridge with 8 extern functions (init, startCall, stopCall, setMute,
  setSpeaker, getStats, forceProfile, destroy) with panic catching
- Kotlin engine layer: WzpEngine JNI wrapper, WzpCallback interface,
  CallStats data class with JSON deserialization
- Jetpack Compose UI: InCallScreen with quality indicator (green/yellow/red),
  mute/speaker/hangup buttons, stats overlay, duration timer
- CallActivity with RECORD_AUDIO permission handling, Material3 theme
- CallService foreground service with WakeLock, WiFi lock, notification
- AudioRouteManager for speaker/earpiece/Bluetooth SCO switching
- AEC wired into CallEncoder pipeline: AEC → AGC → denoise → silence → encode
- AEC farend reference fed from decode path to encode path in pipeline
- Engine exposes set_aec_enabled/set_agc_enabled via AtomicBool flags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:38 +00:00