Skip to content

Get Rust SDK Android Example working#859

Merged
xianshijing-lk merged 5 commits intomainfrom
sxian/CLT-2516/fix_rust_sdk_android_example
Feb 6, 2026
Merged

Get Rust SDK Android Example working#859
xianshijing-lk merged 5 commits intomainfrom
sxian/CLT-2516/fix_rust_sdk_android_example

Conversation

@xianshijing-lk
Copy link
Contributor

@xianshijing-lk xianshijing-lk commented Feb 1, 2026

Summary by CodeRabbit

  • New Features

    • Full Android example UI to connect/disconnect, enter URL/token, and view status.
    • In-app microphone capture and speaker playback with permission prompts and audio controls.
    • Runtime-native integration with graceful fallback, connectivity checks, and playback buffer reporting.
  • Chores

    • Updated Android SDK targets, Kotlin/Compose, Gradle plugins, and Gradle wrapper.
    • Added Android audio permissions and updated project IDE/config ignores.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

Adds a LiveKit audio capture/playback pipeline to the mobile example: Rust core with JNI/FFI, Android Kotlin App wrappers, MediaManager audio I/O, Compose UI, Android build/tooling updates, new permissions, dependency additions, and IDE/gitignore adjustments.

Changes

Cohort / File(s) Summary
Rust core & FFI
examples/mobile/src/lib.rs
Adds audio state, LiveKit connection/room handling, capture/playback buffering, push/pull audio APIs, disconnect/is_connected/get_playback_buffer_size, plus iOS extern and Android JNI shims.
Android Kotlin App API
examples/mobile/android/app/src/main/java/io/livekit/rustexample/App.kt
Wraps native library load with availability flag; exposes connect/disconnect/status/push/pull/getPlaybackBufferSize methods and private JNI stubs.
Android media I/O
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MediaManager.kt
New MediaManager: AudioRecord/AudioTrack threads, permission checks, capture/playback loops, buffering, and app JNI bridging.
Android UI & lifecycle
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt
Adds Compose-based MainScreen, permission flow, connection/audio controls, lifecycle wiring to App and MediaManager, and previews.
Android build files
examples/mobile/android/app/build.gradle, examples/mobile/android/build.gradle, examples/mobile/android/gradle/wrapper/gradle-wrapper.properties
Bumps compile/target SDK to 34, Java 17, Kotlin/Android plugin versions, updates Gradle distribution and wrapper properties.
Gradle wrapper scripts
examples/mobile/android/gradlew, examples/mobile/android/gradlew.bat
Rewrites POSIX-friendly gradlew and adjusts Windows wrapper script (portability, argument quoting, error handling).
Android manifest & permissions
examples/mobile/android/app/src/main/AndroidManifest.xml
Adds RECORD_AUDIO and MODIFY_AUDIO_SETTINGS permissions.
Cargo and Rust deps
examples/mobile/Cargo.toml
Adds futures = "0.3" and parking_lot = "0.12" to examples/mobile dependencies.
IDE configs & gitignore
examples/mobile/android/.idea/AndroidProjectSystem.xml, .../gradle.xml, .../misc.xml, .../vcs.xml, examples/mobile/android/.gitignore
Adds AndroidProjectSystem.xml; removes several .idea files; updates .gitignore to exclude IDE files and generated app/src/main/jniLibs/.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User
  participant Activity as MainActivity
  participant App as Kotlin App
  participant JNI as JNI Bridge
  participant Rust as Rust Core
  participant LiveKit as LiveKit Server

  User->>Activity: Tap "Connect" (URL, Token)
  Activity->>App: connect(url, token)
  App->>JNI: connectNative(url, token)
  JNI->>Rust: livekit_connect(url, token)
  Rust->>LiveKit: Connect & publish local audio
  LiveKit-->>Rust: room events / remote tracks
  Rust-->>JNI: status/event callbacks
  JNI-->>App: connected status
  App-->>Activity: update UI
Loading
sequenceDiagram
  autonumber
  participant Mic as AudioRecord
  participant MM as MediaManager
  participant App as Kotlin App
  participant JNI as JNI Bridge
  participant Rust as Rust Core
  participant Spk as AudioTrack

  Mic->>MM: Capture PCM frames
  MM->>App: pushAudio(samples)
  App->>JNI: pushAudioNative(samples)
  JNI->>Rust: push_audio_capture(samples)
  loop Playback cycle
    Rust-->>JNI: pull_audio_playback(buffer)
    JNI-->>App: pullAudioNative(buffer)
    App-->>MM: pullAudio(buffer)
    MM-->>Spk: Write PCM to speaker
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • davidliu
  • ladvoc
  • theomonnom

Poem

"I’m a rabbit in code, ears tuned to the stream,
JNI tunnels burrow where byte-carrots gleam.
Mic nibs a rhythm, speakers hum back,
Rust grounds the burrow, LiveKit keeps track.
Hoppity audio — stitched, tested, and keen." 🥕🎧

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Get Rust SDK Android Example working' directly reflects the main objective of the PR, which is to make the Rust SDK Android example functional.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sxian/CLT-2516/fix_rust_sdk_android_example

🧹 Recent nitpick comments
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt (1)

171-176: Consider removing pending Handler callbacks in onDestroy.

Once the syntax error in connectToRoom is fixed, the Handler.postDelayed callback could fire after the Activity is destroyed. Consider storing the Handler reference and removing callbacks in onDestroy.

♻️ Suggested improvement
+    private val handler = android.os.Handler(android.os.Looper.getMainLooper())
+
     // In connectToRoom, use: handler.postDelayed({...}, 1000)

     override fun onDestroy() {
         super.onDestroy()
+        handler.removeCallbacksAndMessages(null)
         mediaManager?.release()
         mediaManager = null
         app?.disconnect()
     }
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14e2c8b and af6d0a3.

📒 Files selected for processing (1)
  • examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt
🔇 Additional comments (5)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt (5)

130-136: LGTM!

The disconnect logic properly stops audio, disconnects the app, and updates UI state.


138-160: LGTM!

Permission handling follows the standard Android pattern, and the null-safe handling of mediaManager with appropriate fallback is correct.


179-296: LGTM!

The composable is well-structured with:

  • Clear parameter interface for state and callbacks
  • Appropriate use of remember for local UI state
  • Good security practice with PasswordVisualTransformation on token field
  • Clean conditional rendering based on connection state

298-332: LGTM!

Good coverage of both disconnected and connected UI states in the previews.


62-93: LGTM!

The onCreate implementation properly initializes the App, checks native availability with appropriate error messaging, and wires up the Compose UI with all necessary callbacks.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/mobile/src/lib.rs (1)

401-406: ⚠️ Potential issue | 🟠 Major

Guard FFI inputs to avoid UB/panics.

Line 401-405 assumes non-null UTF‑8; invalid pointers/UTF‑8 will panic or cause UB.

🛠️ Suggested fix
 #[no_mangle]
 pub extern "C" fn livekit_connect(url: *const c_char, token: *const c_char) {
-    let (url, token) = unsafe {
-        let url = CStr::from_ptr(url).to_str().unwrap().to_owned();
-        let token = CStr::from_ptr(token).to_str().unwrap().to_owned();
-        (url, token)
-    };
+    if url.is_null() || token.is_null() {
+        return;
+    }
+    let url = unsafe { CStr::from_ptr(url) };
+    let token = unsafe { CStr::from_ptr(token) };
+    let (url, token) = match (url.to_str(), token.to_str()) {
+        (Ok(u), Ok(t)) => (u.to_owned(), t.to_owned()),
+        _ => return,
+    };
 
     super::livekit_connect(url, token);
 }
🤖 Fix all issues with AI agents
In `@examples/mobile/android/.idea/compiler.xml`:
- Line 4: The compiler.xml currently sets <bytecodeTargetLevel target="21" />
which mismatches build.gradle's Java 17 settings (sourceCompatibility,
targetCompatibility, jvmTarget); fix by making the two consistent—either change
the bytecodeTargetLevel value in compiler.xml to 17 to match build.gradle, or
update build.gradle's sourceCompatibility/targetCompatibility/jvmTarget to 21 if
Java 21 is intended—ensure you update the specific symbol <bytecodeTargetLevel
target="..."/> and the sourceCompatibility/targetCompatibility/jvmTarget entries
so IDE and Gradle targets align.

In `@examples/mobile/android/.idea/deploymentTargetSelector.xml`:
- Line 10: Remove the hardcoded physical device serial entry from the committed
config: delete or replace the <DeviceId ... identifier="serial=R3CW4047CGT" />
line (the DeviceId element containing the serial value "R3CW4047CGT") so no
personal device identifiers are tracked, and add
examples/mobile/android/.idea/deploymentTargetSelector.xml to .gitignore (or
remove the .idea file from repo) to prevent committing machine-specific
IDE/device configs going forward.

In
`@examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt`:
- Around line 109-115: The code currently sets isConnected.value = true and
statusMessage.value = "Connected to room" inside a
Handler(mainLooper).postDelayed(..., 1000), which marks the app connected after
a fixed delay; remove that fixed-delay logic and instead update isConnected and
statusMessage from the actual connection-success callback/event coming from the
native layer (or the LiveKit connection listener) — subscribe to the
room/connection success callback provided by your native binding, call
isConnected.value = true, statusMessage.value = "Connected to room" and
Log.i(TAG, ...) only when that callback fires, and handle connection failure by
setting isConnected false and updating statusMessage accordingly (use the same
identifiers: isConnected, statusMessage, TAG, and replace the
Handler(mainLooper).postDelayed block).
- Around line 35-41: The companion object in MainActivity currently hardcodes a
sensitive JWT in DEFAULT_TOKEN; remove the real token and replace it with a
non-secret placeholder (e.g., "REPLACE_WITH_TOKEN") and update code that reads
DEFAULT_TOKEN to instead load the token at runtime (from secure storage,
BuildConfig field, Intent extras, environment/config, or user input).
Specifically, edit the companion object constant DEFAULT_TOKEN and ensure any
code paths using DEFAULT_TOKEN (in MainActivity) can accept a runtime-supplied
value so secrets are not committed to source control.

In
`@examples/mobile/android/app/src/main/java/io/livekit/rustexample/MediaManager.kt`:
- Around line 240-244: startAll() may leave one audio component running when the
other fails; make it atomic by rolling back any started component on failure.
Call startMicrophone() then startSpeaker(); if startSpeaker() fails but
startMicrophone() returned true, call stopMicrophone() before returning false
(and symmetrically if you call startSpeaker() first). Use startAll(),
startMicrophone(), startSpeaker(), stopMicrophone(), and stopSpeaker() to
implement this rollback and ensure the method returns true only when both starts
succeed.

In `@examples/mobile/android/gradle/wrapper/gradle-wrapper.properties`:
- Line 3: The example project is pinning a pre-release Gradle distribution
("distributionUrl" set to gradle-9.0-milestone-1) which can cause unstable
builds; update the distributionUrl value to use the latest stable Gradle release
(replace "gradle-9.0-milestone-1" with "gradle-9.3.1") so the wrapper points to
the stable bin distribution URL instead of the milestone build.

In `@examples/mobile/src/lib.rs`:
- Around line 112-135: The CI failed rustfmt; run cargo fmt to reformat the
affected lines around the LocalAudioTrack::create_audio_track call and the
room.local_participant().publish_track await chain (the block that constructs
TrackPublishOptions and calls publish_track on LocalTrack::Audio). Re-run
rustfmt/cargo fmt and commit the resulting formatting changes so the
create_audio_track, TrackPublishOptions { ..Default::default() }, and
publish_track(...).await lines conform to the project’s formatter.
- Around line 71-74: The log in function livekit_connect currently prints the
full token (log::info!("Connecting to {} with token {}", url, token)); change
this to avoid leaking credentials by redacting or masking the token before
logging — e.g., log only a safe substring or token length (mask middle
characters) or remove the token from the message entirely; update the log::info
call in livekit_connect to use the masked_token representation instead of the
raw token.
- Around line 466-474: The JNI string conversions in
Java_io_livekit_rustexample_App_connectNative currently call
env.get_string(...).unwrap(), which will panic on JNI errors and cross the FFI
boundary; change this to safely handle the Result by matching or using if let to
detect errors from env.get_string for both url and token, log or report the JNI
error (rather than panicking), and return early from
Java_io_livekit_rustexample_App_connectNative when conversion fails so no panic
crosses FFI; update references to the converted values only after successful
conversion.
🧹 Nitpick comments (2)
examples/mobile/android/.idea/AndroidProjectSystem.xml (1)

1-6: Consider whether .idea/ files should be committed.

IDE-specific configuration files under .idea/ can cause issues when team members use different IDE versions or configurations. Some files (like this one) are benign, but others in this PR contain local paths or machine-specific settings.

Common practice is to either:

  • Exclude most .idea/ files via .gitignore (except codeStyles/ if team shares formatting)
  • Or commit them all for IDE consistency across the team
examples/mobile/android/.idea/misc.xml (1)

1-10: Consider excluding .idea files from version control.

IDE configuration files like misc.xml contain local machine settings (JDK paths, project-specific IDE preferences) that may vary between developers. These files are typically added to .gitignore to avoid unnecessary merge conflicts and to prevent enforcing specific IDE configurations on contributors.

If the intent is to provide a ready-to-use example project, this is acceptable, but consider documenting the JDK 21 requirement in a README instead.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdbca12 and d965187.

⛔ Files ignored due to path filters (3)
  • examples/mobile/android/app/libs/libwebrtc.jar is excluded by !**/*.jar
  • examples/mobile/android/app/src/main/jniLibs/arm64-v8a/libmobile.so is excluded by !**/*.so
  • examples/mobile/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (21)
  • examples/mobile/Cargo.toml
  • examples/mobile/android/.idea/AndroidProjectSystem.xml
  • examples/mobile/android/.idea/codeStyles/Project.xml
  • examples/mobile/android/.idea/codeStyles/codeStyleConfig.xml
  • examples/mobile/android/.idea/compiler.xml
  • examples/mobile/android/.idea/deploymentTargetSelector.xml
  • examples/mobile/android/.idea/gradle.xml
  • examples/mobile/android/.idea/migrations.xml
  • examples/mobile/android/.idea/misc.xml
  • examples/mobile/android/.idea/runConfigurations.xml
  • examples/mobile/android/.idea/vcs.xml
  • examples/mobile/android/app/build.gradle
  • examples/mobile/android/app/src/main/AndroidManifest.xml
  • examples/mobile/android/app/src/main/java/io/livekit/rustexample/App.kt
  • examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt
  • examples/mobile/android/app/src/main/java/io/livekit/rustexample/MediaManager.kt
  • examples/mobile/android/build.gradle
  • examples/mobile/android/gradle/wrapper/gradle-wrapper.properties
  • examples/mobile/android/gradlew
  • examples/mobile/android/gradlew.bat
  • examples/mobile/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (1)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt (1)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/ui/theme/Theme.kt (1)
  • RustexampleTheme (40-70)
🪛 Blinter (1.0.102)
examples/mobile/android/gradlew.bat

[error] 88-88: Complex quote escaping error. Explanation: Complex quote patterns may not be handled correctly by the command interpreter. Recommendation: Use triple-quote pattern """text""" for quotes within quoted strings, or escape properly with variables. Context: Complex quote escaping detected

(E028)

🪛 GitHub Actions: Rust Formatting
examples/mobile/src/lib.rs

[error] 117-117: Rustfmt diff detected at line 117. Run 'cargo fmt' to apply formatting changes.


[error] 127-127: Rustfmt diff detected at line 127. Run 'cargo fmt' to apply formatting changes.

🪛 Gitleaks (8.30.0)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt

[high] 40-40: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test (x86_64-apple-darwin)
  • GitHub Check: Test (x86_64-unknown-linux-gnu)
  • GitHub Check: Test (x86_64-pc-windows-msvc)
🔇 Additional comments (16)
examples/mobile/android/gradle/wrapper/gradle-wrapper.properties (1)

4-5: LGTM!

Adding networkTimeout and validateDistributionUrl are sensible defaults for more robust wrapper behavior.

examples/mobile/android/.idea/migrations.xml (1)

1-10: LGTM!

Standard Android Studio migration configuration using portable $PROJECT_DIR$ reference.

examples/mobile/android/.idea/gradle.xml (1)

3-9: LGTM!

The Gradle configuration updates use portable references (#GRADLE_LOCAL_JAVA_HOME) and the CHOOSE_PER_TEST runner provides flexibility for test execution.

examples/mobile/android/.idea/vcs.xml (1)

5-6: LGTM!

VCS mappings for submodule directories using portable $PROJECT_DIR$ references.

examples/mobile/android/.idea/codeStyles/codeStyleConfig.xml (1)

1-5: LGTM!

Enabling per-project code style settings ensures consistent formatting across team members.

examples/mobile/Cargo.toml (1)

12-13: LGTM!

The futures and parking_lot dependencies are well-established crates appropriate for async audio I/O and efficient synchronization in the LiveKit integration.

examples/mobile/android/.idea/runConfigurations.xml (1)

1-17: Standard IDE configuration.

This suppresses unwanted run configuration suggestions. Same consideration applies as with other .idea files regarding version control inclusion.

examples/mobile/android/app/build.gradle (1)

8-8: LGTM!

The build configuration updates are well-aligned:

  • SDK 34 is the current stable target
  • Java 17 compatibility with matching jvmTarget
  • Kotlin compiler extension 1.5.8 is compatible with Kotlin 1.9.22
  • Dependency versions are recent and consistent

Also applies to: 13-13, 29-34, 40-40, 51-55, 63-63

examples/mobile/android/build.gradle (1)

3-5: LGTM!

Plugin version updates are consistent with the app-level build configuration and align with modern Android tooling.

examples/mobile/android/gradlew.bat (3)

16-17: LGTM on license header addition.

Adding the SPDX license identifier is a good practice for license clarity.


46-53: Improved error handling.

The changes from == to equ for numeric comparisons (ERRORLEVEL) and redirecting errors to stderr (1>&2) follow Windows batch best practices.

Also applies to: 62-66, 81-81


86-89: The original pattern on line 88 is correct; the suggested refactor would change behavior incorrectly.

The pattern if not ""=="%GRADLE_EXIT_CONSOLE%" is a standard, idiomatic Windows batch check for non-empty strings and is documented as a best practice. The suggested alternative if defined GRADLE_EXIT_CONSOLE is not equivalent: it checks whether the variable is defined (exists), whereas the original checks whether it is non-empty. Using if defined would execute the exit regardless of whether GRADLE_EXIT_CONSOLE is set to an empty value, changing the script's intended behavior. No changes needed on line 88.

Likely an incorrect or invalid review comment.

examples/mobile/android/.idea/codeStyles/Project.xml (1)

1-123: LGTM: Project-level code style config is consistent.

No issues spotted in this IDE-only configuration.

examples/mobile/android/gradlew (1)

1-251: LGTM: POSIX wrapper update looks standard.

No issues found.

examples/mobile/android/app/src/main/AndroidManifest.xml (1)

5-7: LGTM: audio permissions added.

Looks appropriate for microphone + audio output use.

examples/mobile/android/app/src/main/java/io/livekit/rustexample/App.kt (1)

1-90: LGTM: native availability guard and API surface look good.

No issues found.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +240 to +244
fun startAll(): Boolean {
val micStarted = startMicrophone()
val speakerStarted = startSpeaker()
return micStarted && speakerStarted
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Rollback partial audio startup on failure.

startAll() can return false while leaving mic or speaker running (Line 240-244). That’s a privacy/UX risk.

🛠️ Suggested fix
     fun startAll(): Boolean {
         val micStarted = startMicrophone()
         val speakerStarted = startSpeaker()
+        if (!micStarted || !speakerStarted) {
+            if (micStarted) stopMicrophone()
+            if (speakerStarted) stopSpeaker()
+        }
         return micStarted && speakerStarted
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun startAll(): Boolean {
val micStarted = startMicrophone()
val speakerStarted = startSpeaker()
return micStarted && speakerStarted
}
fun startAll(): Boolean {
val micStarted = startMicrophone()
val speakerStarted = startSpeaker()
if (!micStarted || !speakerStarted) {
if (micStarted) stopMicrophone()
if (speakerStarted) stopSpeaker()
}
return micStarted && speakerStarted
}
🤖 Prompt for AI Agents
In
`@examples/mobile/android/app/src/main/java/io/livekit/rustexample/MediaManager.kt`
around lines 240 - 244, startAll() may leave one audio component running when
the other fails; make it atomic by rolling back any started component on
failure. Call startMicrophone() then startSpeaker(); if startSpeaker() fails but
startMicrophone() returned true, call stopMicrophone() before returning false
(and symmetrically if you call startSpeaker() first). Use startAll(),
startMicrophone(), startSpeaker(), stopMicrophone(), and stopSpeaker() to
implement this rollback and ensure the method returns true only when both starts
succeed.

Comment on lines +71 to 74
/// Connect to a LiveKit room and set up audio handling
pub fn livekit_connect(url: String, token: String) {
log::info!("Connecting to {} with token {}", url, token);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact access tokens from logs.

Line 73 logs the full token, which can leak credentials via logs.

🛠️ Suggested fix
-    log::info!("Connecting to {} with token {}", url, token);
+    log::info!("Connecting to {}", url);
+    log::debug!("LiveKit token length: {}", token.len());
🤖 Prompt for AI Agents
In `@examples/mobile/src/lib.rs` around lines 71 - 74, The log in function
livekit_connect currently prints the full token (log::info!("Connecting to {}
with token {}", url, token)); change this to avoid leaking credentials by
redacting or masking the token before logging — e.g., log only a safe substring
or token length (mask middle characters) or remove the token from the message
entirely; update the log::info call in livekit_connect to use the masked_token
representation instead of the raw token.

Comment on lines +466 to 474
pub extern "C" fn Java_io_livekit_rustexample_App_connectNative(
mut env: JNIEnv,
_: JClass,
url: JString,
token: JString,
) {
let url: String = env.get_string(&url).unwrap().into();
let token: String = env.get_string(&token).unwrap().into();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and read the relevant section
if [ -f "examples/mobile/src/lib.rs" ]; then
  echo "=== File exists, reading lines 466-480 ==="
  sed -n '466,480p' examples/mobile/src/lib.rs
else
  echo "File not found, listing examples directory structure"
  find examples -type f -name "*.rs" | head -20
fi

Repository: livekit/rust-sdks

Length of output: 533


🏁 Script executed:

# Search for other JNI get_string usage patterns in the file
rg "get_string" examples/mobile/src/lib.rs -A 2 -B 2

Repository: livekit/rust-sdks

Length of output: 269


🏁 Script executed:

# Check if there's error handling pattern for JNI errors in the file
rg "unwrap|expect|match.*Err|log::error" examples/mobile/src/lib.rs | head -20

Repository: livekit/rust-sdks

Length of output: 933


Avoid panics on invalid JNI strings.

Lines 472-473 unwrap get_string(...) results; JNI errors will panic across the FFI boundary causing undefined behavior.

🛠️ Suggested fix
-    let url: String = env.get_string(&url).unwrap().into();
-    let token: String = env.get_string(&token).unwrap().into();
+    let url: String = match env.get_string(&url) {
+        Ok(s) => s.into(),
+        Err(e) => {
+            log::error!("Failed to read url JString: {}", e);
+            return;
+        }
+    };
+    let token: String = match env.get_string(&token) {
+        Ok(s) => s.into(),
+        Err(e) => {
+            log::error!("Failed to read token JString: {}", e);
+            return;
+        }
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub extern "C" fn Java_io_livekit_rustexample_App_connectNative(
mut env: JNIEnv,
_: JClass,
url: JString,
token: JString,
) {
let url: String = env.get_string(&url).unwrap().into();
let token: String = env.get_string(&token).unwrap().into();
pub extern "C" fn Java_io_livekit_rustexample_App_connectNative(
mut env: JNIEnv,
_: JClass,
url: JString,
token: JString,
) {
let url: String = match env.get_string(&url) {
Ok(s) => s.into(),
Err(e) => {
log::error!("Failed to read url JString: {}", e);
return;
}
};
let token: String = match env.get_string(&token) {
Ok(s) => s.into(),
Err(e) => {
log::error!("Failed to read token JString: {}", e);
return;
}
};
🤖 Prompt for AI Agents
In `@examples/mobile/src/lib.rs` around lines 466 - 474, The JNI string
conversions in Java_io_livekit_rustexample_App_connectNative currently call
env.get_string(...).unwrap(), which will panic on JNI errors and cross the FFI
boundary; change this to safely handle the Result by matching or using if let to
detect errors from env.get_string for both url and token, log or report the JNI
error (rather than panicking), and return early from
Java_io_livekit_rustexample_App_connectNative when conversion fails so no panic
crosses FFI; update references to the converted values only after successful
conversion.

@theomonnom
Copy link
Member

theomonnom commented Feb 1, 2026

would be worth discussing whether we should use libwebrtc native mediadevices vs rewriting them in our example

@xianshijing-lk
Copy link
Contributor Author

would be worth discussing whether we should use libwebrtc native mediadevices vs rewriting them in our example

@cloudwebrtc mentioned that before, I wonder how the native mediadevice modules can be integrated into Rust SDK, how large the binary will add, and will it cause conflict problems for users who don't want them.

I will sync with Duan to understand the details better.

And I think it won't be done in this PR.

@xianshijing-lk xianshijing-lk changed the title initial version that seems functional Get Rust SDK Android Example working Feb 3, 2026
@xianshijing-lk
Copy link
Contributor Author

xianshijing-lk commented Feb 3, 2026

Hi teams, I cleaned up the PRs by removing those files that should be gitignored instead.

Please take another look at the PR.

BTW, in case we are going to have the media handling on Rust in the future, I will update the example once that is ready.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt`:
- Around line 95-108: connectToRoom should bail out early when native isn’t
available or credentials are empty: keep the existing app null guard, then add a
preflight that checks if url.isBlank() or token.isBlank(), sets
statusMessage.value to a clear error like "Missing URL or token" and returns;
additionally, if your App object exposes a native-availability flag or method
(e.g., currentApp.isNativeAvailable() or similar), check that and return with
statusMessage.value = "Native not initialized" before initializing MediaManager
or calling currentApp.connect(url, token); update references in this method
(connectToRoom, app/currentApp, mediaManager, statusMessage, currentApp.connect)
accordingly.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d965187 and 14e2c8b.

📒 Files selected for processing (6)
  • examples/mobile/android/.gitignore
  • examples/mobile/android/.idea/gradle.xml
  • examples/mobile/android/.idea/misc.xml
  • examples/mobile/android/.idea/vcs.xml
  • examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt
  • examples/mobile/android/gradle/wrapper/gradle-wrapper.properties
💤 Files with no reviewable changes (3)
  • examples/mobile/android/.idea/gradle.xml
  • examples/mobile/android/.idea/vcs.xml
  • examples/mobile/android/.idea/misc.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/mobile/android/gradle/wrapper/gradle-wrapper.properties
🧰 Additional context used
🧬 Code graph analysis (1)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/MainActivity.kt (1)
examples/mobile/android/app/src/main/java/io/livekit/rustexample/ui/theme/Theme.kt (1)
  • RustexampleTheme (40-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
examples/mobile/android/.gitignore (1)

10-17: LGTM — ignore entries are appropriate for Android Studio metadata and generated JNI libs.

Also applies to: 25-26

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@xianshijing-lk
Copy link
Contributor Author

Any one can take a look ?

…le/MainActivity.kt

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@xianshijing-lk xianshijing-lk merged commit 29e68e9 into main Feb 6, 2026
5 of 9 checks passed
@xianshijing-lk xianshijing-lk deleted the sxian/CLT-2516/fix_rust_sdk_android_example branch February 6, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants