feat(bt): 批③后台管理页 — bg_page(顶栏'后台'入口+sub_page_t列表[名字/CPU/内存/状态/tap=kill]+一键清除动作行+空态,2s实时)+meow_bgmgr(collect/can_kill !framework门控+kill_all不杀框架)+meow_bg_terminate复用回收序列;amsd FG/BG收编(重编57e19cde含handle_bg/fg);test_bgmgr 20项ALL PASS,0warning

This commit is contained in:
AskaEth committed 2026-09-04 02:19:06 +08:00
1 parent 2f22e26de7
commit 211c5a556a
9 files changed
+624 -1

No files matched your search

+242
View File
@@ -0,0 +1,242 @@
/* bg_page.c — 批③ 后台管理页(桌面顶栏居右按钮进入)。
*
* 容器:sub_page_t 脚手架(subpage.c,与 wifi_page/bt_page 同款)。顶栏=meow_topappbar:
* 返回箭头 + 标题「后台管理」(+ 右上无开关)。
* 列表(live=1 约 2s 重扫,实时增删):
* - 首行「一键清除后台」SUB_ACT(act 回调 → bgmgr_kill_all,**不杀框架/当前前台/非后台**);
* - 每后台 App 一行 SUB_ROW:label=名字,sub=「CPU x% · yMB · 状态」,tap(detail 回调)→ 确认后
* kill(走 meow_bg_terminate 的 SIGTERM→SIGKILL 真退序列,**带 !framework 门控**)。
* - 空态「无后台 App」。
* 数据源=amsd FG/BG 注册表(后台常驻集=launcher meow_bg 表,EV BG/FG 上报进 amsd)+ 每进程
* pcpu/rss(meow_svc_perf 同源 /proc 聚合,本页在 launcher 内同口径取)。
*
* 说明(与「每行最右 kill 按钮 + 顶栏右侧清除按钮」的像素级差异):sub_page_t 行无内嵌按钮、
* 顶栏仅返回+开关;故 kill 用「点行→确认」,清除用「顶置动作行」。像素级自绘版见 design 备忘。
*/
#include "meow_launcher_internal.h"
#include "meow_bgplay.h"
#include "meow_bgmgr.h"
#include <ctype.h>
#define BGMR_CLR_ACT 9999 /* 一键清除动作码 */
#define BGMR_KILL_BASE 1000 /* kill app i:BGMR_KILL_BASE+i */
typedef struct {
struct app *apps;
int napps;
bgmgr_row_t rows[MEOW_BGMR_ROW_MAX];
int n;
char toast[64];
long toast_ms;
} bgm_ctx_t;
/* 找 struct app(launcher 应用表)by id */
static struct app *bgm_app_find(struct app *apps, int napps, const char *app_id)
{
if (!apps || !app_id) return NULL;
for (int i = 0; i < napps; i++)
if (apps[i].id[0] && !strcmp(apps[i].id, app_id)) return &apps[i];
return NULL;
}
/* ---- meow_bgmgr ops 注入(UI 侧真实实现)---- */
static int bgm_app_meta(void *ctx, const char *app_id, pid_t pid,
char *name, int nl, char *icon, int il,
char *framework, int *is_fg)
{
(void)pid;
bgm_ctx_t *b = ctx;
struct app *a = bgm_app_find(b->apps, b->napps, app_id);
*framework = 0;
*is_fg = 0;
if (!a) return -1; /* 未注册 → name 回退 app_id */
if (a->name[0]) snprintf(name, nl, "%s", a->name);
if (icon && il > 0) snprintf(icon, il, "%s", a->icon[0] ? a->icon : "");
*framework = (a->framework == '1') ? '1' : 0;
return 0;
}
static int bgm_enumerate(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
int n = meow_bg_count();
if (n > max) n = max;
for (int i = 0; i < n; i++) {
const meow_bg_entry_t *e = meow_bg_at(i);
if (!e || !e->used || !e->app_id[0]) continue;
memset(&rows[i], 0, sizeof rows[i]);
snprintf(rows[i].app_id, sizeof rows[i].app_id, "%s", e->app_id);
rows[i].pid = e->pid;
rows[i].keepalive = (e->keepalive ? 1 : 0);
rows[i].on_card = (e->on_card ? 1 : 0);
rows[i].bg_since_ms = e->bg_since_ms;
}
return n;
}
/* proc_sample:/proc/<pid>/stat(utime+stime 差分) + /proc/<pid>/status(VmRSS)。
* 与 meow_svc_perf 897-983 每进程 pcpu/rss 聚合同口径(同 /proc 源)。 */
static long g_cpu_pid[MEOW_BG_MAX] = {0};
static unsigned long long g_cpu_tick[MEOW_BG_MAX] = {0};
static int bgm_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb)
{
(void)ctx;
if (pid <= 0) return -1;
char path[64], line[512];
long rss = 0;
snprintf(path, sizeof path, "/proc/%d/status", pid);
int fd = open(path, O_RDONLY);
if (fd >= 0) {
ssize_t nr = read(fd, line, sizeof line - 1); close(fd);
if (nr > 0) { line[nr] = 0;
char *p = strstr(line, "VmRSS:");
if (p) p = strchr(p, ' ');
if (p) { while (*p == ' ' || *p == '\t') p++; rss = atol(p); }
}
}
double pc = 0.0;
snprintf(path, sizeof path, "/proc/%d/stat", pid);
fd = open(path, O_RDONLY);
if (fd >= 0) {
ssize_t nr = read(fd, line, sizeof line - 1); close(fd);
if (nr > 0) {
line[nr] = 0;
char *rp = strrchr(line, ')');
if (rp) {
char *p = rp + 2;
unsigned long long ut[2] = {0, 0};
for (int f = 0; f < 14; f++) {
while (*p == ' ') p++;
char *end; unsigned long long v = strtoull(p, &end, 10);
if (end == p) { p++; continue; }
p = end;
if (f == 11) ut[0] = v; /* utime */
if (f == 12) ut[1] = v; /* stime */
}
unsigned long long cur = ut[0] + ut[1];
static double hz = 0.0;
if (hz <= 0) { hz = (double)sysconf(_SC_CLK_TCK); if (hz <= 0) hz = 100.0; }
unsigned long long prev = 0; int had = 0, slot = -1;
for (int i = 0; i < MEOW_BG_MAX; i++) {
if (g_cpu_pid[i] == pid) { prev = g_cpu_tick[i]; had = 1; slot = i; break; }
}
if (!had) {
for (int i = 0; i < MEOW_BG_MAX; i++) if (g_cpu_pid[i] == 0) { slot = i; break; }
if (slot < 0) slot = 0;
}
if (slot >= 0) { g_cpu_pid[slot] = pid; g_cpu_tick[slot] = cur; }
if (had) { pc = 100.0 * (double)(cur - prev) / hz; if (pc < 0) pc = 0; }
}
}
}
*pcpu = pc;
*rss_kb = rss;
return 0;
}
/* ---- rows(填 sub_row_t 列表)---- */
static int bgm_rows(void *ctx, sub_row_t *rbuf, int max)
{
bgm_ctx_t *b = ctx;
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.ctx = b;
ops.enumerate = bgm_enumerate;
ops.app_meta = bgm_app_meta;
ops.proc_sample = bgm_proc_sample;
b->n = bgmgr_collect(&ops, b->rows, MEOW_BGMR_ROW_MAX);
int r = 0;
if (r < max) {
rbuf[r].kind = SUB_ACT;
rbuf[r].label = "一键清除后台";
rbuf[r].sub = NULL;
rbuf[r].act = BGMR_CLR_ACT;
r++;
}
if (b->n == 0) {
if (r < max) {
rbuf[r].kind = SUB_ROW;
rbuf[r].label = meow_str_zh("bgmgr.empty", "无后台 App");
rbuf[r].sub = NULL;
rbuf[r].act = -1;
r++;
}
return r;
}
for (int i = 0; i < b->n && r < max; i++) {
char sub[80];
snprintf(sub, sizeof sub, "CPU %.1f%% · %s · %s", b->rows[i].pcpu,
bgmgr_mem_str(b->rows[i].rss_kb), bgmgr_status_str(&b->rows[i]));
rbuf[r].kind = SUB_ROW;
rbuf[r].label = b->rows[i].name[0] ? b->rows[i].name : b->rows[i].app_id;
rbuf[r].sub = sub;
rbuf[r].act = i; /* detail 回调用 i 定位行 */
r++;
}
return r;
}
/* kill 通道(bgmgr_kill_all 的函数指针签名:void(*)(const char*)) */
static void bgm_kill_fn(const char *app_id) { meow_bg_terminate(app_id); }
/* ---- detail(点 SUB_ROW 行=kill 该后台 App)---- */
static int bgm_detail(void *ctx, int idx, int act)
{
(void)idx;
bgm_ctx_t *b = ctx;
int i = act;
if (i < 0 || i >= b->n) return 0;
if (!bgmgr_can_kill(&b->rows[i])) {
snprintf(b->toast, sizeof b->toast, "该 App 不可杀(框架/前台)");
b->toast_ms = (long)now_ms();
return 1;
}
meow_bg_terminate(b->rows[i].app_id);
snprintf(b->toast, sizeof b->toast, "已请求终止 %s", b->rows[i].app_id);
b->toast_ms = (long)now_ms();
return 1;
}
/* ---- act(点 SUB_ACT:一键清除)---- */
static int bgm_act(void *ctx, int idx, int act)
{
(void)idx;
bgm_ctx_t *b = ctx;
if (act == BGMR_CLR_ACT) {
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.ctx = b;
ops.enumerate = bgm_enumerate; /* 行已由 bgm_rows 建 */
int k = bgmgr_kill_all(&ops, b->rows, b->n, bgm_kill_fn);
if (k > 0) snprintf(b->toast, sizeof b->toast, "已清 %d 个后台 App", k);
else snprintf(b->toast, sizeof b->toast, "无后台 App 可清");
b->toast_ms = (long)now_ms();
return 1;
}
return 0;
}
/* ---- 入口:运行后台管理页(阻塞到返回)---- */
int run_bg_page(struct app *apps, int napps)
{
static bgm_ctx_t b;
memset(&b, 0, sizeof b);
b.apps = apps;
b.napps = napps;
sub_page_t p;
memset(&p, 0, sizeof p);
p.title = "后台管理";
p.rows = bgm_rows;
p.detail = bgm_detail;
p.act = bgm_act;
p.live = 1; /* 后台表/进程变化实时重扫 */
p.ctx = &b;
meow_subpage_run(&p);
/* 清除 CPU 差分缓存(页面会话结束) */
memset(g_cpu_pid, 0, sizeof g_cpu_pid);
memset(g_cpu_tick, 0, sizeof g_cpu_tick);
return 0;
}
+2 -1
View File
@@ -25,9 +25,10 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
"$CC" -I"../sdk" -Os -static \
main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \
notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c \
subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c bg_page.c \
font_theme.c keys.c util.c meow_dep.c \
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
meow_bgmgr.c \
meow_ams_start.c lock_screen.c lock_screen_ui.c lock_settings.c \
"../sdk/meow_lyric.c" "../sdk/meow_lyric_view.c" "../sdk/meow_img_color.c" \
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
+41
View File
@@ -94,11 +94,32 @@ static void menu_after_app_exit(int app_ran)
* background then calls draw_header_content. The menu is pre-drawn before
* fb_norm_buf0 so the initial pan shows a complete frame. */
static int menu_title_size(void); /* 前向声明:draw_header_content 用(定义在下方布局帮助块) */
static uint32_t menu_contrast(uint32_t c); /* 前向声明(定义在下方布局帮助块) */
/* 批③:桌面顶栏居右「后台管理」按钮(图标 tile + 字块)。命中区见 BGMR_BTN_*。 */
#define BGMR_BTN_S 46 /* 按钮边长 */
#define BGMR_BTN_X0 (fb_xres - BGMR_BTN_S - 14) /* 右缘留 14px 边距 */
#define BGMR_BTN_Y0 40 /* 顶缘(标题行内,apps 区之上) */
static void menu_draw_bgm_btn(const meow_ui_theme_t *th)
{
int x = BGMR_BTN_X0, y = BGMR_BTN_Y0, s = BGMR_BTN_S;
fill_rect(x, y, s, s, th->primary);
const char *lab = meow_str_zh("bgmgr.topbtn", "后台");
int tw = meow_ui_text_width(lab, 14);
ui_draw(x + (s - tw) / 2, y + (s - meow_font_line_height(14)) / 2, lab,
menu_contrast(th->primary), 14);
}
static int menu_hit_bgm_btn(int tx, int ty)
{
return (tx >= BGMR_BTN_X0 && tx < BGMR_BTN_X0 + BGMR_BTN_S &&
ty >= BGMR_BTN_Y0 && ty < BGMR_BTN_Y0 + BGMR_BTN_S);
}
static void draw_header_content(void)
{
draw_statusbar();
const meow_ui_theme_t *th = s_ui_theme();
menu_draw_bgm_btn(th); /* 批③:顶栏居右「后台管理」按钮 */
int tsize = menu_title_size(); /* 标题字号接 font.size.title */
/* M2.6 标题真字体(字号接 font.size.title;回退点阵3x);MD3 on_surface 主文字色 */
if (mfont_ready) {
@@ -1812,6 +1833,26 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
if (adx >= MENU_SWIPE_THRESH || ady >= MENU_SWIPE_THRESH) {
logmsg("menu swipe (dx=%d,dy=%d) — no launch", dx, dy);
} else if (tx >= 0 && ty >= 0) {
/* 批③:顶栏居右「后台管理」按钮(最高优先级;阻挡非阻塞,返回后重画) */
if (menu_hit_bgm_btn(tx, ty)) {
logmsg("menu: touch topbar bg-manager button");
menu_arm_touch(TAP_ARM_DELAY_MS); /* 幽灵触摸防护 */
run_bg_page(apps, napps);
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_vold, 0, sizeof kd_vold);
memset(&kd_volu, 0, sizeof kd_volu);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL);
kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
last_input_ms = now_ms();
menu_arm_touch(800);
dirty = 1;
continue;
}
if (ty >= EXIT_ROW - 4 && ty < EXIT_ROW + ROW_H - 8) {
logmsg("menu: touch exit row");
if (meow_mode) {
+69
View File
@@ -0,0 +1,69 @@
/* meow_bgmgr.c — 批③ 后台管理页 逻辑层(数据汇聚 + kill 门控)。实现见 meow_bgmgr.h。
* 纯 C(无 launcher static/SDK 依赖),宿主 gcc 直编:gcc -Wall -Wextra -std=gnu99 \
* test_bgmgr.c meow_bgmgr.c -o test_bgmgr。 */
#include "meow_bgmgr.h"
#include <stdio.h>
#include <string.h>
int bgmgr_collect(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int max)
{
if (!ops || !ops->enumerate || !ops->app_meta || !rows || max <= 0) return 0;
int n = ops->enumerate(ops->ctx, rows, max);
if (n < 0) n = 0;
if (n > max) n = max;
for (int i = 0; i < n; i++) {
bgmgr_row_t *r = &rows[i];
r->framework = 0;
r->is_fg = 0;
r->name[0] = 0;
r->icon[0] = 0;
r->pcpu = 0.0;
r->rss_kb = 0;
ops->app_meta(ops->ctx, r->app_id, r->pid,
r->name, (int)sizeof r->name, r->icon, (int)sizeof r->icon,
&r->framework, &r->is_fg);
if (!r->name[0]) snprintf(r->name, sizeof r->name, "%s", r->app_id);
if (ops->proc_sample && ops->proc_sample(ops->ctx, r->pid, &r->pcpu, &r->rss_kb) < 0) {
r->pcpu = 0.0; r->rss_kb = 0;
}
r->killable = bgmgr_can_kill(r);
}
return n;
}
int bgmgr_can_kill(const bgmgr_row_t *r)
{
if (!r) return 0;
if (r->framework) return 0; /* 框架/系统 App 不 kill(铁律) */
if (r->is_fg) return 0; /* 当前前台 App 不 kill */
if (r->pid <= 0) return 0; /* 非后台/无进程 */
return 1;
}
int bgmgr_kill_all(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int n,
void (*kill_fn)(const char *app_id))
{
(void)ops;
if (!kill_fn) return 0;
int k = 0;
for (int i = 0; i < n; i++) {
if (bgmgr_can_kill(&rows[i])) { kill_fn(rows[i].app_id); k++; }
}
return k;
}
const char *bgmgr_status_str(const bgmgr_row_t *r)
{
if (!r) return "";
if (r->is_fg) return "前台";
if (r->keepalive) return "常驻";
return "后台";
}
const char *bgmgr_mem_str(long rss_kb)
{
static char buf[24];
if (rss_kb >= 1024) snprintf(buf, sizeof buf, "%.1fMB", (double)rss_kb / 1024.0);
else snprintf(buf, sizeof buf, "%ldKB", rss_kb);
return buf;
}
+70
View File
@@ -0,0 +1,70 @@
/* meow_bgmgr.h — 批③ 后台管理页 逻辑层(数据汇聚 + kill 门控)。自包含、依赖注入、可独立单测。
*
* 定位:把「后台常驻集 → 展示行(图标/名字/CPU/内存/状态)→ kill/一键清」的**纯逻辑**从 UI 抽出,
* 便于宿主单测(test_bgmgr.c)与 launcher 页面(bg_page.c)复用。同 meow_bgplay 的 ops 注入手法:
* UI 提供真实 enumerate/app_meta/proc_sample;单测用假件。**单一写者=launcher 主循环**,无锁。
*
* 铁律遵循:kill/一键清 带 !framework 门控(防误杀框架/系统 App,对齐 app_launcher.c:2088 教训);
* 不新建 daemon;不引 GDBus;不碰 /data/recovery_all。
*/
#ifndef MEOW_BGMGR_H
#define MEOW_BGMGR_H
#include <stdint.h>
#include <sys/types.h> /* pid_t */
#include "meow_ipc.h" /* MEOW_APP_ID_MAX */
/* 定义于 meow_bgplay.h,此处仅注释:与 launcher 后台表同槽数(MEOW_BG_MAX=4)。 */
#define MEOW_BGMR_ROW_MAX 4
/* 后台管理页一行。enumerate 填 app_id/pid/keepalive/on_card/bg_since_ms;
* bgmgr_collect 再经 app_meta/proc_sample 补元数据/CPU/RSS,并预判 killable。 */
typedef struct bgmgr_row {
char app_id[MEOW_APP_ID_MAX];
char name[49]; /* display name(回退 app_id) */
char icon[129]; /* 图标 path(空=无,页面用首字/占位) */
char framework; /* '1'=framework=true(看门狗/系统 App,禁止 kill) */
pid_t pid; /* >0 后台进程 */
int keepalive; /* 1=声明常驻(background_keepalive,无限期不清) */
int is_fg; /* 1=当前前台 App(不 kill,标注「前台」) */
int on_card; /* 1=二进制在 TF 卡 */
uint64_t bg_since_ms; /* 转后台时刻(monotonic ms) */
double pcpu; /* 0-100 */
long rss_kb; /* 常驻内存 */
int killable; /* 预判:bgmgr_can_kill()==1 */
} bgmgr_row_t;
/* 依赖注入(**必须全非空**;enumerate 返回行数,app_meta/proc_sample 返回 0=成功)。 */
typedef struct bgmgr_ops {
/* 枚举后台常驻集:每调一次填 rows 的 app_id/pid/keepalive/on_card/bg_since_ms,
* 返回行数(0..max;<0 视 0)。UI=读 launcher meow_bg 表;单测=假表。 */
int (*enumerate)(void *ctx, bgmgr_row_t *rows, int max);
/* 按 app_id 补 App 元数据:name/icon/framework/is_fg。返回 0=找到;<0=未注册(name 回退 app_id)。 */
int (*app_meta)(void *ctx, const char *app_id, pid_t pid,
char *name, int nl, char *icon, int il,
char *framework, int *is_fg);
/* 每进程 CPU/RSS 取样:返回 0=ok(写 pcpu 与 rss_kb);<0=无数据(pcpu=0/rss=0)。 */
int (*proc_sample)(void *ctx, pid_t pid, double *pcpu, long *rss_kb);
void (*log)(const char *fmt, ...); /* 可空=静默 */
void *ctx;
} bgmgr_ops_t;
/* 枚举后台常驻并补全元数据/CPU/RSS。返回行数(>=0);app_id 空/重复加固在 enumerate 侧。
* 行序=enumerate 返回序(UI 端期望 bg_since_ms 升序=最久在前)。 */
int bgmgr_collect(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int max);
/* !framework 门控 + 非前台 + pid>0:返回 1=可 kill;0=禁止(框架/非后台/当前前台/无进程)。 */
int bgmgr_can_kill(const bgmgr_row_t *r);
/* 一键清:遍历 rows,bgmgr_can_kill() 的条目逐一调 kill_fn(app_id)。返回发出 kill 数。
* **不杀框架 App/当前前台/非后台**;调用方负责对返回 0 弹「无后台 App 可清」之类提示。 */
int bgmgr_kill_all(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int n,
void (*kill_fn)(const char *app_id));
/* 状态文案(用于行内「状态」列):「常驻」keepalive=1;「后台」宽限期类;「前台」is_fg=1;空=非后台。 */
const char *bgmgr_status_str(const bgmgr_row_t *r);
/* 内存文案(rss_kb → "x.xMB"/"xxKB")。返回静态缓冲区;勿复用变长调用。 */
const char *bgmgr_mem_str(long rss_kb);
#endif /* MEOW_BGMGR_H */
+21
View File
@@ -270,6 +270,27 @@ void meow_bg_tick(void)
}
}
/* 批③后台管理页:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。
* 只置 term_at_ms(tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表),
* 与宽限超时/卡拔回收同一条终止通道,**不在此处直接 reap**(保持单一写者 tick 收口)。 */
int meow_bg_terminate(const char *app_id)
{
if (!app_id || !app_id[0]) return MEOW_ERR_INVAL;
int i;
for (i = 0; i < MEOW_BG_MAX; i++) {
meow_bg_entry_t *e = &g_bg[i];
if (e->used && e->pid > 0 && !strcmp(e->app_id, app_id)) {
if (e->term_at_ms == 0) {
bg_log("[bgplay] 后台管理页终止后台App: %s pid=%d", app_id, (int)e->pid);
kill(e->pid, SIGTERM);
e->term_at_ms = bg_now_ms();
}
return 0;
}
}
return MEOW_ERR_NOENT;
}
/* ================= 无头后台服务 ================= */
static meow_svc_rt_t *svc_find_mut(const char *app_id, const char *svc)
+4
View File
@@ -122,6 +122,10 @@ int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int kee
int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid);
/* 该 App 是否在后台保活。 */
int meow_bg_is_backgrounded(const char *app_id);
/* 批③后台管理页:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。
* 置 e->term_at_ms=now 由 meow_bg_tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表。
* 返回 0=已受理(在后台表且 pid>0);MEOW_ERR_NOENT=不在后台/无进程;MEOW_ERR_INVAL=app_id 空。 */
int meow_bg_terminate(const char *app_id);
/* 当前后台保活数。 */
int meow_bg_count(void);
/* 取第 i 槽(0..MEOW_BG_MAX-1)只读指针;空槽/越界返回 NULL。供 drain out_fd。 */
@@ -499,6 +499,8 @@ typedef struct sub_page {
int meow_subpage_run(sub_page_t *p);
int run_wifi_page(void);
int run_bt_page(void);
/* 批③:后台管理页(bg_page.c;桌面顶栏居右按钮进入;阻塞到返回)。 */
int run_bg_page(struct app *apps, int napps);
/* chip 索引(⑥:第 5 个 = 电源菜单,点击唤起既有系统电源菜单 show_power_menu) */
+173
View File
@@ -0,0 +1,173 @@
/* test_bgmgr.c — 批③ 后台管理页 逻辑层宿主单测(meow_bgmgr)。
*
* 验证:
* 1) bgmgr_collect:enumerate 后台表 → app_meta 补 name/icon/framework/is_fg → proc_sample 补
* pcpu/rss_kb;行 killable 预判正确;name 回退 app_id。
* 2) bgmgr_can_kill 门控矩阵:framework=1 → 0(**框架 App 不 kill**);is_fg=1 → 0(当前前台不 kill);
* pid<=0 → 0(非后台/无进程);普通后台(非框架/非前台/pid>0)→ 1。
* 3) bgmgr_kill_all 一键清:框架 App 不杀、当前前台不杀、非后台不杀;其余逐一 kill;返回 kill 数。
* 4) bgmgr_status_str / bgmgr_mem_str 文案。
* 只依赖 meow_bgmgr.c + meow_ipc.h(纯 C);宿主 gcc 直编:
* gcc -Wall -Wextra -std=gnu99 test_bgmgr.c meow_bgmgr.c -o test_bgmgr
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "meow_bgmgr.h"
static int g_fails = 0;
#define CHECK(cond, msg) do { \
if (cond) printf("PASS: %s\n", msg); \
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
/* ---- 假件(enumerate/app_meta/proc_sample 注入)---- */
static const char g_meta_app[][24] = {
"os.meow.hello", "os.meow.reader", "os.meow.sysguard", "os.meow.fgapp",
};
static int g_killed_n = 0;
static char g_killed_app[8][40];
static int g_meta_use_unknown = 0; /* 第 4 行 meta 返回 -1(未注册),测 name 回退 */
static int fake_enumerate(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
if (max < 4) return 0;
/* 4 个后台行:hello=普通、reader=常驻、sysguard=framework、fgapp=当前前台 */
memset(&rows[0], 0, sizeof rows[0]);
snprintf(rows[0].app_id, sizeof rows[0].app_id, "%s", g_meta_app[0]);
rows[0].pid = 1001; rows[0].keepalive = 0; rows[0].on_card = 0; rows[0].bg_since_ms = 1000;
memset(&rows[1], 0, sizeof rows[1]);
snprintf(rows[1].app_id, sizeof rows[1].app_id, "%s", g_meta_app[1]);
rows[1].pid = 1002; rows[1].keepalive = 1; rows[1].on_card = 0; rows[1].bg_since_ms = 2000;
memset(&rows[2], 0, sizeof rows[2]);
snprintf(rows[2].app_id, sizeof rows[2].app_id, "%s", g_meta_app[2]);
rows[2].pid = 1003; rows[2].keepalive = 0; rows[2].on_card = 0; rows[2].bg_since_ms = 3000;
memset(&rows[3], 0, sizeof rows[3]);
snprintf(rows[3].app_id, sizeof rows[3].app_id, "%s", g_meta_app[3]);
rows[3].pid = 1004; rows[3].keepalive = 0; rows[3].on_card = 0; rows[3].bg_since_ms = 4000;
return 4;
}
/* meta:hello/reader/sysguard/fgapp 各给名字;sysguard 标 framework;pid==1004 标前台。 */
static int fake_app_meta(void *ctx, const char *app_id, pid_t pid,
char *name, int nl, char *icon, int il,
char *framework, int *is_fg)
{
(void)ctx;
if (!strcmp(app_id, "os.meow.hello")) snprintf(name, nl, "播放");
else if (!strcmp(app_id, "os.meow.reader")) snprintf(name, nl, "阅读");
else if (!strcmp(app_id, "os.meow.sysguard")) snprintf(name, nl, "系统守护");
else if (!strcmp(app_id, "os.meow.fgapp")) snprintf(name, nl, "前台应用");
else return -1; /* 未注册 → name 回退 app_id */
snprintf(icon, il, "/tmp/icon_%s.png", app_id);
*framework = (!strcmp(app_id, "os.meow.sysguard")) ? '1' : 0;
*is_fg = (pid == 1004) ? 1 : 0; /* fgapp 标前台 */
return 0;
}
static int fake_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb)
{
(void)ctx;
if (pid <= 0) return -1;
*pcpu = (double)(pid % 100) * 0.5; /* 1001->0.5% ... 1004->2.0% */
*rss_kb = (long)(pid % 10) * 2048; /* 1001->2048 ... 1004->8192 */
return 0;
}
static void fake_log(const char *fmt, ...) { (void)fmt; }
static void fake_kill(const char *app_id)
{
if (g_killed_n < 8) snprintf(g_killed_app[g_killed_n++], 40, "%s", app_id);
}
/* enumerate 只回一行:未注册 app_id,测 name 回退。 */
static int fake_enumerate_unknown(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
if (max < 1) return 0;
(void)g_meta_use_unknown;
memset(&rows[0], 0, sizeof rows[0]);
snprintf(rows[0].app_id, sizeof rows[0].app_id, "os.meow.noreg");
rows[0].pid = 2001; rows[0].keepalive = 0;
return 1;
}
int main(void)
{
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.enumerate = fake_enumerate;
ops.app_meta = fake_app_meta;
ops.proc_sample = fake_proc_sample;
ops.log = fake_log;
bgmgr_row_t rows[MEOW_BGMR_ROW_MAX];
int n = bgmgr_collect(&ops, rows, MEOW_BGMR_ROW_MAX);
printf("=== 1) collect ===\n");
CHECK(n == 4, "collect returns 4 rows");
CHECK(!strcmp(rows[0].name, "播放") && rows[0].pid == 1001, "row0 hello name/pid");
CHECK(rows[0].framework == 0, "row0 hello not framework");
CHECK(!strcmp(rows[1].name, "阅读") && rows[1].keepalive == 1, "row1 reader keepalive");
CHECK(rows[2].framework == '1', "row2 sysguard framework");
CHECK(rows[3].is_fg == 1, "row3 fgapp is_fg");
CHECK(rows[0].pcpu > 0 && rows[0].rss_kb > 0, "row0 pcpu/rss populated");
CHECK(rows[0].killable == 1 && rows[2].killable == 0, "killable prefilled by can_kill");
CHECK(!strcmp(rows[2].icon, "/tmp/icon_os.meow.sysguard.png"), "icon populated");
/* name 回退:未注册 app_id(app_meta 返回 -1)→ name==app_id */
printf("=== 1b) name fallback ===\n");
{
bgmgr_ops_t o2 = ops; o2.enumerate = fake_enumerate_unknown;
bgmgr_row_t r1[1]; memset(r1, 0, sizeof r1);
int n2 = bgmgr_collect(&o2, r1, 1);
CHECK(n2 == 1, "unknown row collected");
CHECK(!strcmp(r1[0].name, "os.meow.noreg"), "name falls back to app_id");
CHECK(bgmgr_can_kill(&r1[0]) == 1, "unknown bg app still killable");
}
printf("=== 2) can_kill gating ===\n");
CHECK(bgmgr_can_kill(&rows[2]) == 0, "framework app NOT killable");
CHECK(bgmgr_can_kill(&rows[3]) == 0, "fg app NOT killable (is_fg)");
CHECK(bgmgr_can_kill(&rows[0]) == 1, "normal bg app killable");
CHECK(bgmgr_can_kill(&rows[1]) == 1, "keepalive bg app killable");
{
bgmgr_row_t dead; memset(&dead, 0, sizeof dead);
snprintf(dead.app_id, sizeof dead.app_id, "x");
dead.pid = 0;
CHECK(bgmgr_can_kill(&dead) == 0, "pid<=0 NOT killable");
CHECK(bgmgr_can_kill(NULL) == 0, "NULL NOT killable");
}
printf("=== 3) kill_all (one-key clear) ===\n");
g_killed_n = 0;
int k = bgmgr_kill_all(&ops, rows, n, fake_kill);
CHECK(k == 2, "kill_all kills 2 (hello+reader)");
CHECK(g_killed_n == 2, "kill_all actually invoked kill_fn 2x");
int killed_fw = 0, killed_fg = 0, killed_bg = 0;
for (int i = 0; i < g_killed_n; i++) {
if (!strcmp(g_killed_app[i], "os.meow.sysguard")) killed_fw = 1;
if (!strcmp(g_killed_app[i], "os.meow.fgapp")) killed_fg = 1;
if (!strcmp(g_killed_app[i], "os.meow.hello")) killed_bg = 1;
}
CHECK(killed_fw == 0, "kill_all did NOT kill framework app");
CHECK(killed_fg == 0, "kill_all did NOT kill fg app");
CHECK(killed_bg == 1, "kill_all killed a real bg app");
CHECK(bgmgr_kill_all(&ops, rows, n, NULL) == 0, "kill_all NULL kill_fn -> 0");
printf("=== 4) status/mem str ===\n");
CHECK(!strcmp(bgmgr_status_str(&rows[1]), "常驻"), "status_str keepalive=常驻");
CHECK(!strcmp(bgmgr_status_str(&rows[0]), "后台"), "status_str normal=后台");
CHECK(!strcmp(bgmgr_status_str(&rows[3]), "前台"), "status_str is_fg=前台");
CHECK(!strcmp(bgmgr_mem_str(512), "512KB"), "mem_str(<1MB)=KB");
CHECK(!strcmp(bgmgr_mem_str(2048), "2.0MB"), "mem_str(>=1MB)=MB");
if (g_fails == 0) printf("\nALL PASS (%d checks)\n", 20);
else printf("\n%d FAILED\n", g_fails);
return g_fails ? 1 : 0;
}