/* test_bgpage.c — 批③ 后台管理页 UI 按钮化 宿主子测(bg_page.c 按钮命中/顶栏清除命中)。 * * 通过 #include bg_page.c 把其 FILE-STATIC 的 bgm_rows/bgm_btn_rect/bgm_row_tap/bgm_top_action * 平移进本 TU;桩掉 launcher 后台表(meow_bg_count/at/terminate)+ 时钟 + meow_str/logmsg, * 直接验证: * P1 bgm_rows 行骨架:后台 App 行(act=i)+ 空态行(act=-1);name 经 app_meta 正确; * **行副文本指向 ctx 持久缓冲且扛得住栈抖动**(曾指向 bgm_rows 栈帧 → 真机副标题乱码); * P2 bgm_btn_rect 按钮几何:行右 46×34 圆角钮(rx+rw-12-46 定位); * P3 bgm_row_tap 行内命中:**仅按钮区 kill(bgm_terminate 一次),行体/按钮外不误杀**; * **app.json framework=1(SDK 框架 App)照样可杀**(2026-11-04 真机回归); * 受保护/前台行按钮点击 → 不 terminate(门控,留诊断日志);空态行不响应; * P4 bgm_top_action 顶栏清除:kill_all 不杀受保护/前台、杀普通后台(含 framework=1 的); * P5 门控矩阵:bgmgr_can_kill(protected_app/is_fg/pid<=0/普通)。 * 不调用行自绘 bgm_row_draw(纯逻辑),故无需桩 SDK 绘制;行自绘段经 -ffunction-sections/--gc-sections 剔除。 * * 编译(在 patch_v2/meow_launcherd 下): * gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined \ * -ffunction-sections -fdata-sections test_bgpage.c meow_bgmgr.c \ * -Wl,--gc-sections -o test_bgpage * 运行:./test_bgpage */ #include #include #include #include #include "meow_launcher_internal.h" #include "bg_page.c" /* FILE-STATIC 函数平移进本 TU */ /* ---------- 桩:launcher 后台表(meow_bgplay 视角) ---------- */ static meow_bg_entry_t g_bg[MEOW_BG_MAX]; static int g_bg_n; static int g_term_calls; static char g_terminated[16][64]; int meow_bg_count(void) { return g_bg_n; } const meow_bg_entry_t *meow_bg_at(int i) { if (i < 0 || i >= g_bg_n) return NULL; return &g_bg[i]; } int meow_bg_terminate(const char *app_id) { if (g_term_calls < 16) snprintf(g_terminated[g_term_calls++], 64, "%s", app_id); return 0; } unsigned long long now_ms(void) { return 0; } const char *meow_str(const char *id) { (void)id; return NULL; } /* 未知 id → meow_str_zh 回退 zh */ static int g_log_n; void logmsg(const char *fmt, ...) { (void)fmt; g_log_n++; } /* 只计数:门控/未命中应留诊断日志 */ /* 栈抖动:bgm_rows 返回后调用,用来暴露「sub 指向栈帧」的悬垂指针。 */ static void stack_clobber(void) { volatile char buf[256]; memset((void *)buf, 'X', sizeof buf); buf[0] = buf[0]; /* 防优化掉 */ } /* ---------- 测试态:launcher 应用表(app_meta 用) ---------- */ static struct app g_apps[4]; static void set_app(int i, const char *id, const char *name, char fw) { memset(&g_apps[i], 0, sizeof g_apps[i]); snprintf(g_apps[i].id, sizeof g_apps[i].id, "%s", id); snprintf(g_apps[i].name, sizeof g_apps[i].name, "%s", name); g_apps[i].framework = fw; } static void bg_reset(int n, const char *id0, pid_t pid0, const char *id1, pid_t pid1) { g_bg_n = n; g_term_calls = 0; memset(g_bg, 0, sizeof g_bg); if (n >= 1 && id0) { g_bg[0].used = 1; snprintf(g_bg[0].app_id, sizeof g_bg[0].app_id, "%s", id0); g_bg[0].pid = pid0; g_bg[0].keepalive = 0; } if (n >= 2 && id1) { g_bg[1].used = 1; snprintf(g_bg[1].app_id, sizeof g_bg[1].app_id, "%s", id1); g_bg[1].pid = pid1; g_bg[1].keepalive = 0; } } static int g_fails = 0; static int g_checks = 0; #define CHECK(cond, msg) do { \ g_checks++; \ if (cond) printf("PASS: %s\n", msg); \ else { printf("FAIL: %s\n", msg); g_fails++; } } while (0) static int term_count_app(const char *app_id) { int c = 0; for (int i = 0; i < g_term_calls; i++) if (!strcmp(g_terminated[i], app_id)) c++; return c; } int main(void) { static bgm_ctx_t b; memset(&b, 0, sizeof b); b.apps = g_apps; b.napps = 3; set_app(0, "os.meow.hello", "播放", 0); set_app(1, "dev.player.ncm", "流媒体客户端", '1'); /* SDK 框架 App(真机 app.json framework:1) */ set_app(2, "os.meow.sysguard", "系统守护", 0); static sub_row_t rows[SUB_ROWS_MAX]; printf("=== P1) bgm_rows 行骨架 + 副文本持久化 ===\n"); bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520); int rn = bgm_rows(&b, rows, SUB_ROWS_MAX); CHECK(rn == 2, "2 后台 App → 2 行(无空态/无清除动作行)"); CHECK(rows[0].kind == SUB_ROW && rows[0].act == 0, "row0 act=0"); CHECK(!strcmp(rows[0].label, "播放"), "row0 label=名字(播放)"); CHECK(rows[1].kind == SUB_ROW && rows[1].act == 1, "row1 act=1"); CHECK(!strcmp(rows[1].label, "流媒体客户端"), "row1 label=流媒体客户端"); CHECK(rows[1].sub != NULL && !strncmp(rows[1].sub, "CPU ", 4), "row1 sub=CPU/内存/状态"); CHECK(b.n == 2, "bgm_ctx.n=2"); /* 悬垂指针回归:sub 必须落在 ctx 缓冲里,且栈被改写后内容不变 */ uintptr_t subp = (uintptr_t)rows[1].sub; uintptr_t lob = (uintptr_t)(char *)&b; uintptr_t hib = lob + sizeof b; CHECK(subp >= lob && subp < hib, "sub 指向 ctx 持久缓冲(不是 bgm_rows 栈帧)"); char sub_before[80]; snprintf(sub_before, sizeof sub_before, "%s", rows[1].sub); stack_clobber(); CHECK(!strcmp(rows[1].sub, sub_before), "栈抖动后 sub 内容不变(无悬垂)"); printf("=== P2) bgm_btn_rect 按钮几何(行右 46×34) ===\n"); { int bx, by, bw, bh; bgm_btn_rect(0, 0, 480, 56, &bx, &by, &bw, &bh); CHECK(bw == 46 && bh == 34, "按钮 46×34"); CHECK(bx == 480 - 12 - 46, "按钮左缘=行宽-12-46"); CHECK(by == (56 - 34) / 2, "按钮垂直居中"); } printf("=== P3) bgm_row_tap 行内杀按钮命中(仅按钮区 kill,行区不误杀) ===\n"); bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520); bgm_rows(&b, rows, SUB_ROWS_MAX); /* 按钮区命中:行(0,0,480,56),按钮 x 422-468, y 11-45 */ CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 28, 0, 0, 480, 56) == 1, "按钮点击→返回1(重绘)"); CHECK(term_count_app("os.meow.hello") == 1, "按钮点击→terminate hello 一次"); /* 行体(按钮左/其下区)命中 → 不 kill */ CHECK(bgm_row_tap(&b, 0, &rows[0], 200, 28, 0, 0, 480, 56) == 0, "行体点击→返回0(不杀)"); CHECK(term_count_app("os.meow.hello") == 1, "行体点击→不误杀(仍1次)"); CHECK(bgm_row_tap(&b, 0, &rows[0], 410, 28, 0, 0, 480, 56) == 0, "按钮左侧缘(410)→不杀"); CHECK(bgm_row_tap(&b, 0, &rows[0], 475, 28, 0, 0, 480, 56) == 0, "按钮右侧(475)→不杀"); CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 60, 0, 0, 480, 56) == 0, "按钮下方(60)→不杀"); /* 回归(2026-11-04 真机「杀后台按钮按下去没反应」):dev.player.ncm 的 app.json 写 framework:1 * (= 用 SDK 框架,现有 App 全写 1),**必须照杀**——旧实现把它当受保护位,按钮置灰点不动。 */ CHECK(b.rows[1].killable == 1, "framework=1(SDK 框架)App 仍判可杀"); CHECK(bgm_row_tap(&b, 1, &rows[1], 445, 28, 0, 0, 480, 56) == 1, "framework=1 App 按钮→返回1"); CHECK(term_count_app("dev.player.ncm") == 1, "framework=1 App 按钮→真 terminate(无反应 bug 回归)"); /* 真·受保护行(未来系统组件/前台):按钮命中但不 terminate,且留诊断日志 */ bg_reset(2, "os.meow.hello", 1001, "os.meow.sysguard", 1002); bgm_rows(&b, rows, SUB_ROWS_MAX); b.rows[1].protected_app = '1'; g_log_n = 0; CHECK(bgm_row_tap(&b, 1, &rows[1], 445, 28, 0, 0, 480, 56) == 1, "受保护行按钮→返回1(重绘)"); CHECK(term_count_app("os.meow.sysguard") == 0, "受保护行按钮→不 terminate"); CHECK(g_log_n >= 1, "受保护行按钮→留一行诊断日志(可查「点了没反应」)"); printf("=== P3b) 空态行不响应 ===\n"); bg_reset(0, NULL, 0, NULL, 0); int rn0 = bgm_rows(&b, rows, SUB_ROWS_MAX); CHECK(rn0 == 1 && rows[0].act == -1, "0 后台 App → 1 空态行(act=-1)"); CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 28, 0, 0, 480, 56) == 0, "空态行点击→返回0(不杀)"); g_term_calls = 0; printf("=== P4) 顶栏清除(一键清除后台)不杀受保护/前台 ===\n"); bg_reset(2, "os.meow.hello", 1001, "os.meow.sysguard", 1002); bgm_rows(&b, rows, SUB_ROWS_MAX); b.rows[1].protected_app = '1'; CHECK(bgm_top_action(&b) == 1, "顶栏清除按钮:返回 1(重绘约定)"); CHECK(term_count_app("os.meow.hello") == 1, "清除→terminate 普通后台 hello"); CHECK(term_count_app("os.meow.sysguard") == 0, "清除→不杀受保护 sysguard"); /* 回归:framework=1 的 SDK 框架 App 也在「一键清」范围内 */ bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520); bgm_rows(&b, rows, SUB_ROWS_MAX); g_term_calls = 0; CHECK(bgm_top_action(&b) == 1, "framework=1 App:一键清返回 1"); CHECK(term_count_app("dev.player.ncm") == 1, "一键清→terminate framework=1 App(回归)"); /* 前台 App(is_fg)不杀:给 hello 标前台再清 */ bg_reset(1, "os.meow.hello", 1001, NULL, 0); bgm_rows(&b, rows, SUB_ROWS_MAX); for (int i = 0; i < b.n; i++) if (!strcmp(b.rows[i].app_id, "os.meow.hello")) b.rows[i].is_fg = 1; g_term_calls = 0; CHECK(bgm_top_action(&b) == 1, "前台 App 仍在:顶栏清除返回 1(重绘)"); CHECK(g_term_calls == 0, "前台 App → kill_all 不杀(0 terminate)"); CHECK(bgm_top_action(&b) == 1, "二次清除(仍前台/不可杀):返回 1"); CHECK(g_term_calls == 0, "二次清除 → 仍不 terminate"); printf("=== P5) 门控矩阵(bgmgr_can_kill) ===\n"); { bgmgr_row_t r; memset(&r, 0, sizeof r); r.pid = 5; CHECK(bgmgr_can_kill(&r) == 1, "普通后台可杀"); r.protected_app = '1'; CHECK(bgmgr_can_kill(&r) == 0, "protected_app 不可杀"); r.protected_app = 0; r.is_fg = 1; CHECK(bgmgr_can_kill(&r) == 0, "前台不可杀"); r.is_fg = 0; r.pid = 0; CHECK(bgmgr_can_kill(&r) == 0, "pid<=0 不可杀"); } if (g_fails == 0) printf("\nALL PASS (%d checks)\n", g_checks); else printf("\n%d FAILED\n", g_fails); return g_fails ? 1 : 0; }