/* test_vol_ramp_jack.c — 宿主单测:音量 ramp 的插孔消抖 + 时长上限(2026-09-01)。 * * 背景(真机日志实证):4.4 耳机插入瞬间,插孔电气瞬态在音量键 ADC(jz_adc_aux, * event2)上产生 VOL_UP 幽灵按住,launcher vol_ramp 从 24% 一路爬到上限 50 * (225391「volume UP <- ramp」→ 227213「ramp end -> 50」,紧随 audiod「jack changed」)。 * * 编译运行(在 patch_v2/meow_launcherd 下): * gcc -Wall -Wextra -Os -I../sdk \ * -DJACK_HS_STATE='"/tmp/tvrj_hs"' -DJACK_BAL_STATE='"/tmp/tvrj_bal"' \ * test_vol_ramp_jack.c -o test_vol_ramp_jack && ./test_vol_ramp_jack * 全 PASS -> exit 0。 * * 手法:#include "audio.c" 同 TU 平移(同 test_notif_tf.c 惯例);now_ms 用假时钟 * (手动推进,3s 上限与 1s 节流都可瞬时测);meow_settings_* 打内存桩。 */ /* ---- 桩(先定义,audio.c 经内部头拿到声明) ---- */ #include #include #include #include static unsigned long long g_fake_now = 100000; /* 假时钟(ms),手动推进 */ unsigned long long now_ms(void); /* 前置声明,防与头文件顺序问题 */ unsigned long long now_ms(void) { return g_fake_now; } static void advance_ms(unsigned long long d) { g_fake_now += d; } void logmsg(const char *fmt, ...); void logmsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, " [log] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } /* meow_settings 内存桩:audio.c 只用 get_int("audio.volume*")/set_int/save */ static int g_store_volume = 24; static int g_store_max = 50; static int g_store_step = 1; static int g_save_count = 0; extern int settings_volume; /* audio.c 的缓存全局(同 TU #include 后链接可见) */ extern int settings_volume_max; extern int settings_volume_step; int meow_settings_get_int(const char *key, int *out) { if (!strcmp(key, "audio.volume")) { *out = g_store_volume; return 0; } if (!strcmp(key, "audio.volume_max")) { *out = g_store_max; return 0; } if (!strcmp(key, "audio.volume_step")) { *out = g_store_step; return 0; } return -1; } int meow_settings_get_enum(const char *key, int *out) { (void)key; (void)out; return -1; } int meow_settings_get_bool(const char *key, int *out) { (void)key; (void)out; return -1; } int meow_settings_set_int(const char *key, int v) { /* 真代码经 settings_sync_cache_listener 把 backend 值同步到 audio.c 的 settings_volume * 缓存全局;桩里直接同步,保持同一语义。 */ if (!strcmp(key, "audio.volume")) { g_store_volume = v; settings_volume = v; return 0; } return -1; } int meow_settings_save(void) { g_save_count++; return 0; } /* util.c 符号桩(audio.c 引用,宿主无 util.c) */ int read_file_int(const char *path); int read_file_int(const char *path) { FILE *f = fopen(path, "r"); if (!f) return -1; int v = -1; if (fscanf(f, "%d", &v) != 1) v = -1; fclose(f); return v; } /* 其它模块全局桩(settings_sync_cache_* 引用;本测试不走该路径,仅满足链接) */ int screen_off_timeout = 0; int screen_off_mode = 0; int power_longpress_menu_ms = 0; int power_forceoff_ms = 0; int refresh_rate_cfg = 60; int refresh_always_max = 0; #include "audio.c" static int g_pass = 0, g_fail = 0; static void check(const char *name, int got, int want) { if (got == want) { g_pass++; printf("PASS %s\n", name); } else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); } } static void write_file(const char *path, int v) { FILE *f = fopen(path, "w"); if (!f) { perror(path); exit(2); } fprintf(f, "%d\n", v); fclose(f); } /* 驱动 ramp 直到停止或步数超限;held=key_held 恒值;每步推进 40ms * (模拟真机每步 ~52ms 的 amixer system() 开销:0→100 全程 ~4s,必然越过 3s 上限, * 而常见 24→50 窗口 ~1s 不受影响)。 */ static int drive_ramp(int held, int max_steps) { int steps = 0; while (vol_ramp_dir != 0 && steps < max_steps) { advance_ms(40); vol_ramp_tick(held); steps++; } return steps; } /* 场景起始音量:backend 桩与 audio.c 缓存全局一起置(真代码由 settings_load 同步)。 */ static void set_vol(int v) { g_store_volume = v; settings_volume = v; } /* 音量上限/跳步同理:vol_clamp 读的是 audio.c 的 settings_volume_max/settings_volume_step。 */ static void set_max(int v) { g_store_max = v; settings_volume_max = v; } int main(void) { /* T0 基线回归:无插孔节点(文件不存在)→ 单击起 ramp,到 target 即停(只跳 step 格)。 */ unlink(JACK_HS_STATE); unlink(JACK_BAL_STATE); set_vol(24); set_max(50); g_store_step = 1; g_save_count = 0; settings_volume_adj(+1); check("T0 ramp started", vol_ramp_dir != 0, 1); drive_ramp(0, 100); check("T0 click stops at target 25", g_store_volume, 25); check("T0 ramp stopped", vol_ramp_dir, 0); /* T1 长按续爬到上限 50(无插孔干扰,正常长按体验不受消抖影响)。 */ set_vol(24); g_save_count = 0; settings_volume_adj(+1); drive_ramp(1, 5000); /* 24→50=26 步 ×40ms≈1s,远未到 3s 上限 */ check("T1 long-press reaches max 50", g_store_volume, 50); check("T1 stopped at max", vol_ramp_dir, 0); /* T2 时长上限:max 提到 100(0→100 需 ~100 步 >3s 窗)→ 3s 强制停,不打满。 * 假时钟 40ms/步 → 3s=75 步 <100 步(4s);drive 300 步必触发 cap。 */ set_vol(0); set_max(100); g_save_count = 0; settings_volume_adj(+1); drive_ramp(1, 300); check("T2 ramp force-stopped by cap", vol_ramp_dir, 0); check("T2 volume below max (not pinned)", g_store_volume < 100, 1); check("T2 volume climbed (>0)", g_store_volume > 0, 1); printf(" (T2 stopped at volume=%d after cap)\n", g_store_volume); /* T3 插孔消抖:prime 插孔状态(hs=0,bal=0),随后模拟插入 4.4(bal=1)。 * 变化后 800ms 内:vol_ramp_begin 被忽略;在爬的 ramp 被 tick 中止。 */ set_max(50); set_vol(24); write_file(JACK_HS_STATE, 0); write_file(JACK_BAL_STATE, 0); advance_ms(JACK_POLL_MS + 10); settings_volume_adj(+1); /* 这次调用顺带 prime 插孔缓存(无变化→不消抖) */ drive_ramp(0, 100); check("T3 prime: normal click still works", g_store_volume, 25); write_file(JACK_BAL_STATE, 1); /* 模拟 4.4 插入 */ advance_ms(JACK_POLL_MS + 10); /* 过节流窗,下次检测即发现变化 */ set_vol(24); g_save_count = 0; settings_volume_adj(+1); /* 此调用检测变化 → 边沿被忽略 */ check("T3 press ignored in debounce window", vol_ramp_dir, 0); check("T3 volume untouched", g_store_volume, 24); /* T3b 在爬的 ramp 被插孔变化中止(幽灵按住场景:ramp 已起,随后检测到插孔变化)。 */ /* 先让消抖窗过期:推进 >800ms,再正常起 ramp(长按)。 */ advance_ms(JACK_DEBOUNCE_MS + 100); set_vol(24); settings_volume_adj(+1); check("T3b ramp restarted after window", vol_ramp_dir, 1); vol_ramp_tick(1); advance_ms(20); vol_ramp_tick(1); /* 爬了两步 */ int before = g_store_volume; write_file(JACK_HS_STATE, 1); /* 又插 3.5(变化) */ advance_ms(JACK_POLL_MS + 10); /* 过节流窗 */ vol_ramp_tick(1); /* 检测到变化 → 中止 */ check("T3b in-flight ramp aborted by jack change", vol_ramp_dir, 0); check("T3b volume froze at abort point", g_store_volume, before); printf(" (T3b aborted at volume=%d)\n", g_store_volume); /* T3c 消抖窗过后恢复正常(误伤可忽略验证)。 */ advance_ms(JACK_DEBOUNCE_MS + 100); settings_volume_adj(+1); check("T3c press works again after window", vol_ramp_dir, 1); drive_ramp(0, 100); unlink(JACK_HS_STATE); unlink(JACK_BAL_STATE); printf("\n%d pass, %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; }