diff --git a/android/app/src/main/java/com/wzp/engine/WzpEngine.kt b/android/app/src/main/java/com/wzp/engine/WzpEngine.kt index bfd05cf..39e73fa 100644 --- a/android/app/src/main/java/com/wzp/engine/WzpEngine.kt +++ b/android/app/src/main/java/com/wzp/engine/WzpEngine.kt @@ -159,6 +159,18 @@ class WzpEngine(private val callback: WzpCallback) { private external fun nativeWriteAudioDirect(handle: Long, buffer: java.nio.ByteBuffer, sampleCount: Int): Int private external fun nativeReadAudioDirect(handle: Long, buffer: java.nio.ByteBuffer, maxSamples: Int): Int private external fun nativeDestroy(handle: Long) + + companion object { + init { System.loadLibrary("wzp_android") } + + /** Get the identity fingerprint for a seed hex. No engine needed. */ + @JvmStatic + private external fun nativeGetFingerprint(seedHex: String): String? + + /** Compute the full identity fingerprint (xxxx:xxxx:...) from a seed hex string. */ + @JvmStatic + fun getFingerprint(seedHex: String): String = nativeGetFingerprint(seedHex) ?: "" + } private external fun nativePingRelay(handle: Long, relay: String): String? private external fun nativeStartSignaling(handle: Long, relay: String, seed: String, token: String, alias: String): Int private external fun nativePlaceCall(handle: Long, targetFp: String): Int @@ -208,11 +220,6 @@ class WzpEngine(private val callback: WzpCallback) { return nativeAnswerCall(nativeHandle, callId, mode) } - companion object { - init { - System.loadLibrary("wzp_android") - } - } } /** Integer constants matching the Rust [CallState] enum ordinals. */ diff --git a/android/app/src/main/java/com/wzp/ui/call/InCallScreen.kt b/android/app/src/main/java/com/wzp/ui/call/InCallScreen.kt index 598a0db..d4b3740 100644 --- a/android/app/src/main/java/com/wzp/ui/call/InCallScreen.kt +++ b/android/app/src/main/java/com/wzp/ui/call/InCallScreen.kt @@ -431,14 +431,16 @@ fun InCallScreen( Spacer(modifier = Modifier.height(20.dp)) - // Identity - val fp = if (seedHex.length >= 16) seedHex.take(16) else "" + // Identity — compute real fingerprint from seed + val fullFp = remember(seedHex) { + if (seedHex.length >= 64) com.wzp.engine.WzpEngine.getFingerprint(seedHex) else "" + } Row(verticalAlignment = Alignment.CenterVertically) { - if (fp.isNotEmpty()) { - Identicon(fingerprint = seedHex, size = 28.dp) + if (fullFp.isNotEmpty()) { + Identicon(fingerprint = fullFp, size = 28.dp) Spacer(modifier = Modifier.width(8.dp)) CopyableFingerprint( - fingerprint = fp.chunked(4).joinToString(":"), + fingerprint = fullFp, style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace), color = TextDim ) diff --git a/crates/wzp-android/src/jni_bridge.rs b/crates/wzp-android/src/jni_bridge.rs index 76cae17..5fe7469 100644 --- a/crates/wzp-android/src/jni_bridge.rs +++ b/crates/wzp-android/src/jni_bridge.rs @@ -363,6 +363,31 @@ pub unsafe extern "system" fn Java_com_wzp_engine_WzpEngine_nativePingRelay<'a>( .unwrap_or(JObject::null().into_raw()) } +/// Get the identity fingerprint for a seed hex string. +/// Returns the full fingerprint (xxxx:xxxx:...) or empty string on error. +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_wzp_engine_WzpEngine_nativeGetFingerprint<'a>( + mut env: JNIEnv<'a>, + _class: JClass, + seed_hex_j: JString, +) -> jstring { + let seed_hex: String = env.get_string(&seed_hex_j).map(|s| s.into()).unwrap_or_default(); + let fp = if seed_hex.is_empty() { + String::new() + } else { + match wzp_crypto::Seed::from_hex(&seed_hex) { + Ok(seed) => { + let id = seed.derive_identity(); + id.public_identity().fingerprint.to_string() + } + Err(_) => String::new(), + } + }; + env.new_string(&fp) + .map(|s| s.into_raw()) + .unwrap_or(JObject::null().into_raw()) +} + // ── Direct calling JNI functions ── /// Start persistent signaling connection to relay for direct calls.