Skip to content

fix(android): restore Core HTTPS and wss:// TLS after the reqwest 0.13 upgrade - #1253

Open
DepengWang wants to merge 1 commit into
Open-Less:betafrom
DepengWang:fix/android-tls-crypto-provider
Open

DepengWang wants to merge 1 commit into
Open-Less:betafrom
DepengWang:fix/android-tls-crypto-provider

Conversation

@DepengWang

@DepengWang DepengWang commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Since #1117 moved openless-core to reqwest 0.13, an Android build from beta cannot reach any cloud provider. Every LLM request, every provider "Verify", and WebSocket cloud ASR (e.g. Volcengine) fail. Local ASR still works, and desktop is fine. No published release is affected yet: the last release (v2.0.2-Beta.1, 10-17) and the last Android CI APK (10-19) both predate #1117. The next release would ship it.

There are two independent causes, both rustls panics inside a spawned task. Nothing reaches the log: Rust panics don't go to logcat, and the UI only sees the failure. #1250 has since fixed the second cause in run(); this PR fixes the first one and closes the remaining Android gap in the second.

1. rustls-platform-verifier is never initialized on Android (HTTPS)

reqwest 0.13's rustls feature verifies certificates through rustls-platform-verifier. On Android, that crate calls the platform TrustManager through JNI. It panics on the first HTTPS request (Expect rustls-platform-verifier to be initialized) unless:

  • Rust first hands it the JVM and an application Context (init_with_env), and
  • its Kotlin half, the org.rustls:rustls-platform-verifier AAR, is in the APK.

Neither was done.

  • Rust: new src/android/tls_verifier.rs initializes the verifier from nativeRegisterActivityContext, which OpenLessApplication.onCreate calls before Core sends anything. The crate is built on jni-rs 0.22 while this crate uses 0.21, so 0.22 is added as jni022 and the raw JNIEnv / Context pointers are passed across. Initialization is idempotent. An error is logged ([android-tls]) instead of unwinding across JNI.
  • Gradle: new scripts/patch-android-rustls-verifier.mjs, in the same style as patch-android-shizuku-deps.mjs. It patches the generated project:
    • AAR dependency. The version is read from src-tauri/Cargo.lock (rustls-platform-verifier-android), so the Kotlin and Rust halves cannot drift. It resolves from the project's official Maven archive on GitHub. A local Maven copy (OPENLESS_RUSTLS_MAVEN, or <OPENLESS_CACHE_ROOT>/rustls-maven) is preferred when present, for offline or proxied builds.
    • Manifest. The AAR's manifest sets an app-wide android:networkSecurityConfig. It forbids cleartext everywhere except a list of CA CRL hosts. That would silently override our usesCleartextTraffic placeholder, for example the cleartext the debug build allows for local providers. It is removed with tools:remove.
      • The trade-off is that CRL downloads over HTTP now follow the app's own cleartext policy, so on release builds (cleartext off) they are blocked.
      • The verifier checks revocation in soft-fail mode, so an unreachable CRL does not fail the handshake. Certificate chain and hostname validation are unaffected.
      • If you would rather keep online CRL checks on release, the alternative is to merge the AAR's CRL domain list into our own config. I kept this PR minimal.
    • R8. A keep rule for org.rustls.platformverifier.**, since Rust looks those classes up by name.

2. No process-default rustls CryptoProvider (wss:// ASR)

tokio-tungstenite's rustls-tls-webpki-roots connector builds its ClientConfig from the process-default CryptoProvider. The graph now enables both rustls providers: ring through this crate, aws-lc-rs through reqwest 0.13. So rustls cannot choose one and panics on the first wss:// connect ("Could not automatically determine the process-level CryptoProvider"). In the log, the ASR session goes to error ~130 ms after [asr] Volcengine connect, with no message.

  • Already fixed by fix(desktop): Fix and organize the new version, and refine the version. #1250: tls_crypto::install_tls_crypto_provider() installs ring first thing in run().
  • What this PR adds: tls_verifier::init calls the same function. On Android, OpenLessApplication.onCreate registers the Context before any Activity enters run(), so a process that only hosts the keyboard has a provider before its first dictation. The call is idempotent.

Build paths

The patch step is added next to patch-android-shizuku-deps.mjs everywhere the Gradle project is prepared:

  • .github/workflows/android-apk.yml and ci.yml
  • scripts/build-android-local.sh and scripts/run-android-tauri-dev.mjs
  • android/README.md

Compatibility

  • Android APK grows by the ~10 KB AAR. No new permissions. The app's cleartext policy is unchanged.
  • Desktop code is untouched; the Rust changes are all behind cfg(target_os = "android").
  • No settings, storage or sync changes.

Test plan

  • Before the fix: Android arm64 build of beta (df73ad07), plus my own non-network patches, on a OnePlus CPH2573 and a Xiaomi M2011K2C. LLM verify, ASR verify (Volcengine) and cloud dictation all failed. Windows on the same commit was fine.

  • Fix 1 only: LLM verify and polish worked, ASR verify still failed.

  • Fix 1 + 2: on the OnePlus, ASR verify, LLM verify and Volcengine cloud dictation all pass on a debug build and on a release build (Rust LTO + R8). CertificateVerifier is present in the release dex after R8, and the merged manifest has no networkSecurityConfig.

  • Provider installed from both run() and the Context registration: rebuilt as a debug build on the OnePlus, with ASR verify, LLM verify and cloud dictation all passing. This was tested before rebasing onto fix(desktop): Fix and organize the new version, and refine the version. #1250, with an equivalent function of my own in lib.rs. After the rebase, the same ring install comes from fix(desktop): Fix and organize the new version, and refine the version. #1250's tls_crypto. The rebased commit itself was not re-tested on a device.

  • node scripts/patch-android-rustls-verifier.test.mjs (contract checks):

    • the Gradle and manifest patches are idempotent;
    • the version comes from Cargo.lock;
    • the cleartext policy is kept;
    • the Rust init and the Android provider install are wired;
    • every Android build path runs the patch.

    It also runs under npm test.

  • cargo check --lib --locked for x86_64-pc-windows-msvc and aarch64-linux-android on the rebased commit.

🤖 Generated with Claude Code

reqwest 0.13 in openless-core verifies certificates through
rustls-platform-verifier, which panics on Android until it is given the
JVM and application Context, so every LLM request and provider
validation died inside its task. Initialize it when OpenLessApplication
registers its Context, and wire the org.rustls:rustls-platform-verifier
AAR into the generated Gradle project (version read from Cargo.lock, the
AAR's app-wide networkSecurityConfig removed so the app keeps its own
cleartext policy, classes kept for R8) on every Android build path.

The same registration also installs the process-default CryptoProvider
(tls_crypto, Open-Less#1250) that tokio-tungstenite's wss:// ASR clients need,
since it runs before any Activity enters run().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DepengWang
DepengWang force-pushed the fix/android-tls-crypto-provider branch from 838d7d1 to 2bc7c4e Compare October 22, 2026 10:30
@DepengWang

Copy link
Copy Markdown
Contributor Author

提醒一下:main 刚同步到 beta(f0744e10),已经包含 #1117 的 reqwest 0.13 升级,但还没有这个修复。如果在它合入前从 main / beta 发 Android 版,Android 上的 LLM 润色、各渠道「验证」和 WebSocket 云端识别都会失败(本地识别和桌面端不受影响)。麻烦在下次发 Android 版之前看一下这个 PR,谢谢!

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.

1 participant