语言切换知识库 - 对抗审查记录

审查范围:01/02/03/04 共 4 篇文档 审查基准:本仓 /home/zbc/car/MiCarSettings 实际源码 审查人:Claude (对抗审查员) 日期:2026-07-19

共确认问题 13 条,其中:高 4,中 6,低 3。


高严重度

H1 | 04 篇 | 入口 ③ 描述(行 170) | 机制臆造:把 framework LocalePicker 错认成本项目自带的 display.language.LocalePicker

  • 文档原文:“onSelectTabChangedLocalePicker.updateLocale(locale),调用的是 本项目自带的 com.android.car.settings.miauto.display.language.LocalePicker(别和 com.android.internal.app.LocalePicker 混淆)”
  • 源码正解:
    • settingsPage/micarDisplaySettings/src/main/java/com/android/car/settings/miauto/display/LanguagePickerPrefController.java:28 import com.android.internal.app.LocalePicker; —— import 的就是 framework 类
    • 该文件第 46 行 LocalePicker.updateLocale(locale); 调用的是 framework 的 updateLocale
    • settingsPage/micarDisplaySettings/src/main/java/com/android/car/settings/miauto/display/language/LocalePicker.java 是项目自带类(包名 com.android.car.settings.miauto.display.language),但 LanguagePickerPrefController 根本没有引用它(grep 全仓只找到 LocalePickerInjector 在同包内引用)
  • 修正建议:改为”调用的是 framework com.android.internal.app.LocalePicker.updateLocale(与 display/language/LocalePicker.java 同名但完全不同)“。注意 display/language/LocalePicker.java 真正的入口是它自己的 updateLocales(LocaleList) 方法,用 am.updatePersistentConfiguration(config),见 H2。

H2 | 02 篇 | 入口归一性断言(行 207) | 断言错误:项目内一处直接调 IActivityManager.updatePersistentConfiguration

  • 文档原文:“项目内没有任何一处直接调用 IActivityManager.updatePersistentConfigurationSettings.System.putStringsetprop 来改 locale。”
  • 源码反证:settingsPage/micarDisplaySettings/src/main/java/com/android/car/settings/miauto/display/language/LocalePicker.java:186
    am.updatePersistentConfiguration(config);
    方法签名 public static void updateLocales(LocaleList locales)(line 178),与 framework 走的是另一条写入路径。
  • 缓解:此 LocalePicker.java 已被 XML 注释停用(04 篇自己也说”已废弃”),实际不会跑;但断言写得过于绝对。
  • 修正建议:把”没有任何一处”改为”除已废弃的 display/language/LocalePicker.java:186 外,活跃路径上没有任何一处…”。

H3 | 02 篇 | 整篇大量 framework 行为过于具体,但本仓无法验证 | 应降级为描述性

  • 涉及位置(多处):
    • §1 三存储点表格(行 12-17):“system_locales 优先级最高 / persist.sys.locale 次高 fallback / Configuration 派生值”
    • §2 调用链描述(行 41-58):“AMS.updatePersistentConfiguration() 通过 SettingsProvider.updateSystemSettingLocked() 写 system_locales”、“Android 13 之后 system_locales 是权威落点”
    • §4 决策树(行 132-148):“SystemServer 启动 → 读 system_locales → 非空赋 mCurrentConfig / 空 fallback 读 persist.sys.locale”
    • §5 时序图(行 167-192):AMS 内部”写 system_locales / 同步 persist.sys.locale”步骤
    • §8 实战复盘(行 248-273):基于”system_locales 出厂锁 zh-CN → 走绿色路径 C”的推理
    • §9 一句话结论(行 300)
  • 问题:以上全部是 AOSP framework 内部行为(com.android.internal.app.LocalePicker、AMS、SettingsProvider),这些类都不在本仓。文档未明确标注”本仓不可验证”,容易让读者误以为是基于本仓代码得出的结论。
  • 修正建议:在 §2 / §4 / §5 顶部加 banner:“以下 framework 链路基于 AOSP android-13.0.0_r53 公开实现描述,本仓不含相关源码,不能从本仓验证。“实战复盘 §8 标注”基于 AOSP 行为推测”。

H4 | 03 篇 | 行 70 + 行 116 | ECE XML preference 数量错误:说”4 个 + 占位”,实际只有 3 个真正的 Preference

  • 文档原文:
    • 行 70:“ECE 版本只有 4 个 preference(系统版本 / 关于-参数信息 / 开发者选项 + 占位)”
    • 行 103 (mermaid):“加载 ECE 精简 XML…只含 4 个 preference”
  • 源码正解:settingsPage/settingsSystem/src/main/res/xml/miauto_universal_settings_fragment_ece.xml 共 3 个 Preference:
    1. SystemInfoPreference(line 13-18,系统版本)
    2. BasicPreference + AboutSettingsPreferenceController(line 31-36,关于-参数&信息)
    3. BasicPreference + DeveloperOptionsEntryPreferenceController(line 37-44,开发者选项)
    SpacePreference 是间距占位(line 7-8, 47-48),不是业务 Preference。
  • 修正建议:把”4 个 + 占位”改为”3 个真正的 Preference(系统版本/关于/开发者选项)+ SpacePreference 间距占位”。

中严重度

M1 | 04 篇 | 行 171 表 ④ | 彩蛋”无 region 限制”不严谨

  • 文档原文:“国内可用:是(无 region 限制,硬编码 sChangeLanguageEnabled = true)”
  • 源码正解:
    • settingsPage/settingsSystem/src/main/java/com/android/car/settings/system/KernelVersionPreferenceController.java:40 sChangeLanguageEnabled = true 硬编码 ✓
    • 该 Controller 内部确实无 region 判断
    • 但是!XML 层 settingsPage/settingsSystem/src/main/res/xml/micar_about_settings_fragment.xml:39 写明 settings:hiddenFeatures="@string/micar_common_settings_lemans_ece",而 base/settingsBaseUi/src/main/res/values/strings.xml:91 <string name="micar_common_settings_lemans_ece">lemans_ece</string> —— ECE 版本会隐藏内核版本条目,根本点不到。
  • 修正建议:改为”代码层无 region 判断、硬编码开启;但 XML micar_about_settings_fragment.xml:39 通过 hiddenFeatures 在 ECE 版隐藏条目(lemans_ece),ECE 版用户点不到。国内 cn 版正常可见。“

M2 | 03 篇 | 行 110 mermaid 节点 | isGlobalRegion 标签和实际语义混淆,自相矛盾

  • 文档原文:mermaid CheckEU -- "否 → isGlobalRegion=true<br/>(region == 'global' 或其他非 cn/eu/ece 值)" --> GLOBAL_BRANCH
  • 源码正解:base/settingsBaseLib/src/main/java/com/android/car/settings/miauto/common/DeviceUtil.java:104-106
    public static boolean isGlobalRegion() {
        return BUILD_REGION_GLOBAL.equals(BUILD_REGION);  // 仅 "global" == "global"
    }
    即 isGlobalRegion() 仅当 region == “global” 时返回 true;如果 region 是 “fr” / “us” / 任何非 cn/eu/ece/global 值,isGlobalRegion() 返回 false
  • 修正建议:节点标签改为”否 → 进入 GLOBAL_BRANCH(含 isGlobalRegion=true 和其他未识别值);isGlobalRegion 仅在 region==‘global’ 时为 true”,避免把”进入 GLOBAL_BRANCH”和”isGlobalRegion=true”等同。

M3 | 03 篇 | 行 91-95 代码片段 | MiCarSettings.isEce() 代码片段漏行

  • 文档原文:
    // MiCarSettings.java:407-411
    public static boolean isEce() {
        String region = SystemProperties.get(ROM_BUILD_REGION, "");
        return TextUtils.equals(region, ECE_REGION);
    }
  • 源码正解(base/settingsBaseLib/src/main/java/com/android/car/settings/miauto/common/views/MiCarSettings.java:407-411):
    public static boolean isEce() {
        String region = SystemProperties.get(ROM_BUILD_REGION, "");
        MLog.i("isEce() region=" + region);    // ← 文档遗漏
        return TextUtils.equals(region, ECE_REGION);
    }
  • 修正建议:补上 MLog.i(...) 一行,或者把行号改为 408+410-411 表示省略。

M4 | 02 篇 | 调用点 #3 描述(行 204) | “toast 提示”描述误导

  • 文档原文:“工具类快速切换 toast 提示,二选一切换 zh/en”
  • 源码正解(settingsPage/settingsSystem/src/main/java/com/android/car/settings/utils/LanguageUtils.java:17-44):showTipToast 内部实际是弹 AlertDialog.MiCarBuilder 弹框(builder.setPositiveButton/setNegativeButton/create/show),不是 toast 提示;toast 是切换成功后顺带弹出的反馈(line 27-28)。方法名 showTipToast 本身具有误导性,但实际是个 Dialog。
  • 修正建议:把”toast 提示”改为”确认弹框(AlertDialog.MiCarBuilder,方法名虽叫 showTipToast 但实际弹的是 Dialog)“。

M5 | 02 篇 | 行 226-235 LocaleChangedReceiver 代码切片 | 简化掉 companion object 和 MLog.d

  • 文档原文(Kotlin 代码):
    class LocaleChangedReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (TextUtils.equals(Intent.ACTION_LOCALE_CHANGED, intent?.action)) {
                VoiceSearchManager.getInstance().onLocaleChanged()
            }
        }
    }
  • 源码正解(app/src/main/java/com/android/car/settings/miauto/LocaleChangedReceiver.kt:14-26):
    • 实际还有 companion object { private const val TAG = "LocaleChangedReceiver" }(line 16-18)
    • onReceive 第一行是 MLog.tag(TAG).d("Receive locale changed broadcast.")(line 21)
  • 影响:低,简化展示可以接受,但建议加 // 简化展示 注释。

M6 | 02 篇 | 行 209 待核实项 | “global flavor 是否有第二条切换路径” 实际已可结案

  • 文档原文:“待核实:MiCarSettings 是否在某些 flavor(如 global 海外)有第二条切换路径(例如 IActivityManager 直接调用),目前 grep 未发现…”
  • 实际:经全仓 grep LocalePicker.updateLocale|updatePersistentConfiguration 共 5 处(详见 H2),其中只有 display/language/LocalePicker.java:186am.updatePersistentConfiguration,且该类无任何调用方(已废弃)。所以已可结案:global flavor 没有第二条活跃路径。
  • 修正建议:把”待核实”改为”已核实(2026-07-19):全仓仅 5 处写入点,1 处已废弃,无 flavor 私有路径”。

低严重度

L1 | 04 篇 | 行 228 | 中文 strings 行号范围”175-181”不精确

  • 文档原文:“中文译文(values-zh-rCN/strings.xml:175-181):标题’切换语言’,按钮’取消/确定’”
  • 源码正解(settingsPage/settingsSystem/src/main/res/values-zh-rCN/strings.xml):
    • 175: micar_change_locale_dialog_title “切换语言”
    • 176: (应该是其他 string,未在文档列出)
    • 177: micar_change_locale_dialog_centent
    • 178: micar_change_locale_dialog_btn_cancel “取消”
    • 179: micar_change_locale_dialog_btn_confirm “确定”
    • 180: (其他)
    • 181: micar_setting_changed_language
  • 影响:低,范围给得太宽。建议改为列出实际行号。

L2 | 01 篇 | 行 110 mermaid 节点 | 列表生成时未提兜底逻辑(仅”命中 currentLocale 的那条 isSelected=true”)

  • mermaid 节点:“按固定顺序生成 10 条 LocaleInfoWrapper…命中 currentLocale 的那条 isSelected=true”
  • 源码:SystemLanguageConfig.kt:62-77 实际还有”zh+CN 兜底选 zh 那条”逻辑(精确匹配失败时)。
  • 影响:低,关键代码切片 §4(行 234-239)已说明兜底。但 mermaid 节点描述不完整。建议补一句”未命中且有 zh-CN 兜底”。

L3 | 04 篇 | 行 170 入口 ③ “Tab 切换中英文” | 描述需要补充上下文

  • 文档原文:“settingsPage/micarDisplaySettings/.../miauto/display/LanguagePickerPrefController.java:34-68 + 自带的 display/language/LocalePicker.java:22-208
  • 源码正解:
    • LanguagePickerPrefController.java 范围 34-68(class 体)✓
    • display/language/LocalePicker.java 总行数 208 ✓,但 LanguagePickerPrefController 并未调用它(见 H1),把它列为”实现要点”是误导。
  • 影响:中-低,与 H1 是同源问题。建议删掉”+ 自带的 display/language/LocalePicker.java:22-208”或注明”该类无活跃调用方”。

4 篇之间的一致性检查

通过 ✓

  • region prop key ro.micar.build.region:03 篇(行 15)、04 篇(行 13/54)一致,均与 DeviceUtil.java:29 + MiCarSettings.java:179 吻合。
  • 入口 ① 移除逻辑行号 72-78:03 篇(行 152 :69-80)、04 篇(行 270 :72-78)一致。
  • LocaleChangedReceiver 注册位置 AndroidManifest.xml:1583-1589:02 篇(行 215)、04 篇(行 159)一致。
  • 4 个 LocalePicker.updateLocale 调用点(02 篇 §6 表)vs 4 个入口(04 篇 §4 表):组织方式不同但实质对应。
  • system_universal_language_title / system_universal_language_switch_confirm 文案:01 篇表格与源码 strings.xml:325-326 / zh-rCN:295-296 完全一致。
  • 彩蛋 5 个 strings 文案:04 篇 §4.3 与源码 strings.xml:204-209 完全一致。

不通过(已计入上面)

  • 02 篇”4 个调用点归一”vs 04 篇”4 个入口”——本身一致,但 02 篇漏了 display/language/LocalePicker.java:186 这条 IActivityManager 直调(H2)。

最该修正的 3 件事(按 ROI 排序)

  1. 【必修】04 篇入口 ③ 描述(H1 + L3):删掉”调用的是本项目自带的 display.language.LocalePicker”这一段。LanguagePickerPrefController 调的是 framework 类,display/language/LocalePicker.java 与它无关。这是机制臆造,会直接误导后续维护者。

  2. 【必修】02 篇入口归一性断言(H2 + M6):把”项目内没有任何一处直接调用 IActivityManager.updatePersistentConfiguration”改为”活跃路径上没有…(已废弃的 display/language/LocalePicker.java:186 是唯一一处)“,同时把”待核实 global flavor 第二路径”结案。

  3. 【强烈建议】02 篇 framework 行为加不可验证标注(H3):在 §2/§4/§5/§8 顶部统一加 banner:“以下 framework 链路基于 AOSP 公开实现描述,不在本仓”。这能让读者清楚区分”本仓已验证”与”AOSP 行为推断”,避免今后踩坑时把推断当事实。