Skip to content

fix(android): 荣耀畅玩 20 设置页保存凭据/切换供应商立即「操作失败」 #865

Description

@HKLHaoBin

环境

项目 信息
机型 荣耀畅玩 20(KOZ-AL00 等)
系统 Magic UI 4.0(Android 10 系,系统更新长期停留在 4.0.x)
WebView 通常为厂商内置 Huawei/Honor WebView(Chromium 版本偏旧,且难以切换为 Google WebView)
应用 OpenLess Android APK(com.openless.app

现象

设置 → 服务(AI 提供商) 页面:

  • 切换 LLM / ASR 供应商
  • 保存 API Key / 密钥
  • 保存 接口地址(endpoint)
  • 保存 模型 ID

操作时右上角 SavedToast 先显示 「保存中」common.saving),随即变为 「操作失败」common.operationFailed)。几乎立即失败,无明显网络等待。

初步怀疑为 WebView 版本过旧导致前端兼容性问题,但需结合后端日志区分是 IPC 桥、偏好写入还是 Android Keystore 凭据加密链路。

前端行为(错误被吞掉)

所有上述操作最终都走 ProvidersSection.tsx 的 SavedToast 事件:

  1. 切换供应商onLlmProviderChange / onAsrProviderChange

    • emitSaved('saving')setActiveLlmProvider / setActiveAsrProviderupdatePrefs → 可能再 setCredential 写入默认 endpoint/model
    • 任一步 catch 后:emitSaved('failed', t('common.operationFailed'))不展示 Rust 返回的具体错误
    • 相关代码:openless-all/app/src/pages/settings/ProvidersSection.tsx(约 L239–340)
  2. 凭据字段保存CredentialField.save

    • setCredential(account, value) → Tauri set_credential
    • 失败:showTemporaryStatus('saveError') → 同样映射为 common.operationFailed
    • 相关代码:同文件约 L807–821
  3. IPC 调用链

    • openless-all/app/src/lib/ipc/asr-credentials.tsinvokeOrMock('set_credential' | 'set_active_*_provider', ...)
    • openless-all/app/src/lib/ipc/shared.ts:依赖 window.__TAURI_INTERNALS__ + @tauri-apps/api/coreinvoke

若应用能正常打开设置页并显示当前偏好,说明 Tauri invoke 基础通路大概率可用;失败更可能发生在具体 command 的业务逻辑(尤其凭据落盘),而非 WebView 完全无法跑 JS。

后端可能失败点(Android 优先排查)

A. Android Keystore 加密凭据(高优先级)

#838 起,Android 凭据写入 credentials.enc.json 前经 Android Keystore AES-GCM 封装:

  • Kotlin:openless-all/app/android/kotlin/OpenLessCredentialVault.kt
  • Rust:openless-all/app/src-tauri/src/persistence/android_credentials.rs
  • 持久化:openless-all/app/src-tauri/src/persistence/credentials.rssave_android_credentials / write_verified

切换供应商保存密钥/地址/模型都会触发 CredentialsVault::set*save_credentials → Keystore seal + 原子写文件。低端荣耀机型上 Keystore2 / TEE 实现不完整或繁忙时,JNI 可能返回 TEMPORARILY_UNAVAILABLE 等,最终到前端只剩「操作失败」。

B. 偏好写入 set_settings

切换供应商还会 updatePrefsset_settingspreferences.json)。若仅凭据字段失败而切换供应商偶发成功,可缩小到 A;若全部失败,需同时查 B。

C. WebView / 前端兼容性(待验证)

Honor 畅玩 20 上常见旧 Huawei WebView(Chromium 90–99 量级)。若日志显示 invoke 未到达 Rust 或 JS 侧 Unhandled promise rejection,再查:

  • 动态 import('@tauri-apps/api/core') 是否异常
  • CustomEvent / createPortalSelectLite)等是否在特定 WebView 下触发同步异常

当前代码中 Chromium 102+ 才假设可用的能力主要在样式层(如 _atoms.tsx 注释),与保存链路无直接耦合,故 WebView 作为次要假设

建议复现与采集日志

adb logcat -c
# 手动:打开设置 → 服务,切换供应商或保存密钥
adb logcat -d -v time > android-provider-save.log
Select-String -Path .\android-provider-save.log -Pattern `
  "RustStdoutStderr","panicked","set_credential","set_settings",`
  "keystore","credentials","Android credential","openless-keystore",`
  "ProvidersSection","invoke","FATAL"

同时记录:

  • 设置 → 应用 → Android System WebView / Huawei WebView 版本号
  • 开发者选项中的 WebView 实现包名
  • 失败是「首次配置」还是「已有凭据后修改」

建议修复方向

  1. 短期:Android 设置页保存失败时,将 invoke 返回的 String 错误(或映射后的可读文案)写入 SavedToast / logClientError,避免一律 operationFailed
  2. 中期:针对 Keystore TEMPORARILY_UNAVAILABLE 做有限重试;失败时给出「凭据加密服务不可用」类明确提示。
  3. 若确认为 WebView:评估最低 Chromium 版本、在启动时检测 WebView UA 并提示升级系统 WebView 包;避免在旧 WebView 上使用未 polyfill 的 API。

关联代码索引

文件 说明
ProvidersSection.tsx 保存 UI + 泛化「操作失败」
lib/savedEvent.ts SavedToast 事件
commands/credentials.rs set_credential / set_active_*_provider
persistence/credentials.rs Android save_android_credentials
persistence/android_credentials.rs Keystore 信封读写
android/kotlin/OpenLessCredentialVault.kt Keystore JNI 后端

验收

  • 荣耀畅玩 20(或同 Magic UI 4.0 + 旧 WebView 机型)可完成供应商切换与凭据保存
  • 失败时用户能看到可行动的错误信息(非笼统「操作失败」)
  • 日志可区分 Keystore / 文件 IO / IPC / 前端异常

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions