Skip to content

feat(android): 将 Android APK 运行时移植到 beta#650

Merged
H-Chris233 merged 3 commits into
Open-Less:betafrom
HKLHaoBin:openless-android-beta-to-beta
Jun 12, 2026
Merged

feat(android): 将 Android APK 运行时移植到 beta#650
H-Chris233 merged 3 commits into
Open-Less:betafrom
HKLHaoBin:openless-android-beta-to-beta

Conversation

@HKLHaoBin

@HKLHaoBin HKLHaoBin commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

摘要

无关联 upstream issue(不填写 Fixes 行)。

在 beta 分支上引入 Android debug APK 可构建、可安装、可 ADB 测试 的完整运行时层:Tauri mobile 入口、Rust/Kotlin/前端分层、CI workflow,以及应用内听写 + 悬浮窗/无障碍脚手架。桌面 macOS/Windows 行为通过 #[cfg(not(mobile))] / #[cfg(mobile)] 隔离,不改变现有桌面语义。

修复 / 新增 / 改进

兼容

  • 不包含:
    • Android 本地 ASR(Foundry / Sherpa / Qwen 等桌面能力)
    • IME v2 跨 App 输入(manifest/代码已 scaffold,非 v1 默认路径)
    • Release / 签名 APK 与 Play 分发
    • 移动端自动更新(supportsAutoUpdate: false
    • iOS 支持
  • 对现有用户 / 本地环境 / 构建流程的影响:
    • 桌面用户:无行为变更预期;热键、托盘、胶囊、QA 浮窗、本地 ASR 仍仅在 #[cfg(not(mobile))] 路径编译
    • 桌面开发者:现有 npm run tauri dev/build 流程不变;新增 Android 构建需 ANDROID_HOME / NDK_HOME 与 Rust Android targets(仅主动跑 tauri:android:* 时需要)
    • CI:新增独立 Android workflow,不阻塞现有 cross-platform CI;ci.yml 仅多 workflow_dispatch 触发器
    • 依赖Cargo.toml Android 目标增加 ndk-contexttao Android 等;桌面 target 依赖树不变

测试计划

  • 命令:gh workflow run "Android APK (debug)" --repo HKLHaoBin/openless --ref openless-android-beta-to-betagh workflow run CI --repo HKLHaoBin/openless --ref openless-android-beta-to-beta

  • 结果:两次 run 均 success(commit 1ea467a

  • 证据路径:

  • 命令(真机,可选但推荐):adb install -r ci-artifacts/OpenLess-android-debug-arm64-v8a-*.apkadb shell am start -n com.openless.app/.MainActivity → 应用内录音 + 设置页权限面板

  • 结果:APK 安装启动无 native 符号崩溃(__cxa_pure_virtual 已修复);ndk-contextmobile_runtime.rs 启动时初始化

  • 证据路径:本地 android-crash-only.log(不提交 git,见 AGENTS.md 阶段 1/5)

HKLHaoBin and others added 3 commits June 12, 2026 17:55
Sync android-apk.yml from openless-android so the workflow appears in Actions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@HKLHaoBin HKLHaoBin changed the title Openless android beta to beta feat(android): 将 Android APK 运行时移植到 beta Jun 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
Android credentials are stored with only base64 encoding, not encrypted. This is a clear security vulnerability (plaintext secrets on disk). Additionally, the HMAC integrity check for history files has been removed entirely, allowing undetected tampering of dictation history on all platforms. Both issues should be addressed before release.

⚡ Recommended focus areas for review

Sensitive Data Exposure

Android credentials are stored using only base64 encoding without any encryption (load_android_credentials and save_android_credentials). The comment explicitly marks it as "Stub: base64 envelope — replace with Keystore-backed AES when JNI lands." This means API keys and tokens are effectively plaintext on the file system, directly readable by any process with file access (e.g., on a rooted device). You must not ship stub encryption; implement Keystore-backed AES or at least encrypt with a derived key before merging to beta.

#[cfg(target_os = "android")]
fn android_credentials_path() -> Result<PathBuf> {
    Ok(data_dir()?.join(ANDROID_CREDENTIALS_FILE))
}

#[cfg(target_os = "android")]
fn load_android_credentials() -> Result<Option<CredsRoot>> {
    let path = android_credentials_path()?;
    if !path.exists() {
        return Ok(None);
    }
    let bytes = fs::read(&path).with_context(|| format!("read failed: {}", path.display()))?;
    if bytes.is_empty() {
        return Ok(None);
    }
    // Stub: base64 envelope — replace with Keystore-backed AES when JNI lands.
    use base64::Engine;
    let decoded = base64::engine::general_purpose::STANDARD
        .decode(bytes)
        .context("decode android credentials envelope")?;
    let root =
        serde_json::from_slice::<CredsRoot>(&decoded).context("parse android credentials json")?;
    Ok(Some(root))
}

#[cfg(target_os = "android")]
fn save_android_credentials(root: &CredsRoot) -> Result<()> {
    let cleaned = clean_credentials(root);
    let json = serde_json::to_string(&cleaned).context("encode credentials failed")?;
    use base64::Engine;
    let encoded = base64::engine::general_purpose::STANDARD.encode(json.as_bytes());
    let path = android_credentials_path()?;
    ensure_dir(path.parent().unwrap_or_else(|| Path::new(".")))?;
    fs::write(&path, encoded).with_context(|| format!("write failed: {}", path.display()))?;
    Ok(())
}
Missing Integrity Check

The entire HMAC-based integrity verification for dictation history (history_hmac_key, read_history_with_key, write_history_with_key, and all related functions) has been unconditionally removed. This means history.json integrity is no longer verified on any platform (including desktop). Previously (issue #609), tampered history would be detected and fail-safe; now an attacker can silently modify history without detection, and the corrupted data may be fed to downstream LLM polishing. Ensure the integrity check is preserved for non-mobile targets (i.e., keep #[cfg(not(mobile))] around the HMAC logic, not deleted entirely).

fn read_locked(&self) -> Result<Vec<DictationSession>> {
    read_or_default::<Vec<DictationSession>>(&self.path)
}

fn write_locked(&self, sessions: &[DictationSession]) -> Result<()> {
    let json = serde_json::to_vec_pretty(sessions).context("encode history failed")?;
    atomic_write(&self.path, &json)
}
Potential Data Loss

In data_dir() for Android, if the TAURI_ANDROID_APP_DATA_DIR environment variable is not set, the function falls back to temp_dir().join("OpenLess"). Temporary directories are cleared on reboot, meaning all persistent data (credentials, history, preferences) would be lost after a device restart. Tauri should always set this env var, but if it doesn't, the fallback will cause silent data loss. Either require the env var to be set (return an error) or use a proper Android app-specific directory.

#[cfg(target_os = "android")]
{
    if let Ok(dir) = std::env::var("TAURI_ANDROID_APP_DATA_DIR") {
        return Ok(PathBuf::from(dir).join("OpenLess"));
    }
    Ok(std::env::temp_dir().join("OpenLess"))
}

@H-Chris233 H-Chris233 merged commit 81b7cff into Open-Less:beta Jun 12, 2026
4 checks passed
pull Bot pushed a commit to soitun/openless that referenced this pull request Jun 13, 2026
Open-Less#650 引入的 AURA / 玻璃拟态界面大改回退到 beta-3 (v1.3.6-3) 的外观,同时保留
AURA 期间合入的功能与今日两处行为修复。

回退到 beta-3:
- 外壳 FloatingShell、概览 / 历史页、卡片按钮 _atoms、设置弹窗、tokens.css / global.css
- 移除随 AURA 加入的主题切换(themeMode.ts / ThemeSection.tsx)
- 设置关闭按钮去掉圆形底(beta-3 本就透明)

保留:Apple 语音、手机远程录入、安卓、划词追问等功能,后端不动。

今日行为修复:
- 风格页切换不再卡顿(prefs:changed 监听只订阅一次 + motion layoutDependency 限制 layout 重测)
- ASR 本地引擎不再锁死,可在 provider 页直接切换(Apple 语音 macOS 直接可选)

为划词追问页补回其引用的 6 个 solid / radius token,避免回退后丢色。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants