diff --git a/patch_v2/meow_amsd/meow_amsd.c b/patch_v2/meow_amsd/meow_amsd.c index db74c9a..774f133 100644 --- a/patch_v2/meow_amsd/meow_amsd.c +++ b/patch_v2/meow_amsd/meow_amsd.c @@ -956,6 +956,41 @@ static void handle_out(char **t, int nt) out_append(pkg, pid, epoch, up, text); } +/* 批①后台常驻上报(单向 best-effort,launcher → amsd): + * EV BG → AMS_BG(转后台保活) + * EV FG → AMS_FG(回前台)。旧 launcher 不发、本层不识别也不崩。 */ +static void handle_bg(char **t, int nt) +{ + if (nt < 2) return; + const char *pkg = t[0]; + pid_t pid = (pid_t)atoi(t[1]); + long epoch = (nt > 2) ? atol(t[2]) : (long)time(NULL); + long up = (nt > 3) ? atol(t[3]) : read_uptime_sec(); + struct ams_app *a = app_get(pkg); + if (!a) return; + a->state = AMS_BG; + a->pid = pid; + char detail[192]; + snprintf(detail, sizeof detail, "pid=%ld", (long)pid); + hist_push(a, "bg", detail, epoch, up); + emit_log("BG %s pid=%ld", pkg, (long)pid); +} + +static void handle_fg(char **t, int nt) +{ + if (nt < 1) return; + const char *pkg = t[0]; + long epoch = (nt > 1) ? atol(t[1]) : (long)time(NULL); + long up = (nt > 2) ? atol(t[2]) : read_uptime_sec(); + struct ams_app *a = app_find(pkg); + if (!a) return; + a->state = AMS_FG; + char detail[192]; + snprintf(detail, sizeof detail, "bg->fg"); + hist_push(a, "fg", detail, epoch, up); + emit_log("FG %s", pkg); +} + /* ---- §6.2 二期:picker 编排(amsd 路由权威) ---- */ static struct ams_picker *picker_find(const char *action) @@ -1252,6 +1287,8 @@ static int dispatch_line(char *line, char *resp, int rn) if (!strcmp(tok[0], "LAUNCH")) { handle_launch(tok + 1, nt - 1); } else if (!strcmp(tok[0], "EXIT")) { handle_exit(tok + 1, nt - 1); } else if (!strcmp(tok[0], "OUT")) { handle_out(tok + 1, nt - 1); } + else if (!strcmp(tok[0], "BG")) { handle_bg(tok + 1, nt - 1); } + else if (!strcmp(tok[0], "FG")) { handle_fg(tok + 1, nt - 1); } return 0; } diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 0d5542b..1e15d5a 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -123,6 +123,24 @@ static void ams_report_exit(const struct app *a, int status, long long dur_ms) ams_report_exit_id(a->id, status, dur_ms); } +/* 批①后台常驻上报(单向 best-effort):转后台 `EV BG `、回前台 `EV FG `。 + * amsd 收到后置 AMS_BG/AMS_FG(批③后台管理页元数据来源);旧 launcher 不发、amsd 不识别也不崩。 */ +static void ams_report_bg(const char *app_id, pid_t pid) +{ + if (!app_id || !app_id[0]) return; + char line[256]; + int l = snprintf(line, sizeof line, "EV BG %s %d\n", app_id, (int)pid); + if (l > 0) ams_send(line, l); +} + +static void ams_report_fg(const char *app_id) +{ + if (!app_id || !app_id[0]) return; + char line[256]; + int l = snprintf(line, sizeof line, "EV FG %s\n", app_id); + if (l > 0) ams_send(line, l); +} + /* ---- stdout/stderr 管道捕获(AMSD_DESIGN §5):前台 waitpid 循环排空 + 按行转发 ---- */ static char g_ams_out_line[1024]; /* 未完结行缓冲(无 \n) */ static int g_ams_out_n = 0; @@ -319,6 +337,7 @@ static void meow_acct_sync(const char *app_id, const struct app *ap) a->framework = ap->framework; a->background = ap->background; /* M4 */ a->background_audio = ap->background_audio; + a->background_keepalive = ap->background_keepalive; /* 批① */ strncpy(a->icon, ap->icon, 128); app_name_copy(a->name, (int)sizeof a->name, ap->name); strncpy(a->exec, ap->exec, 128); @@ -648,6 +667,7 @@ void meow_parse_app_json(FILE *f, struct app *a) int fonts = 0, fonts_set = 0; /* app.json "fonts":<档>(0=默认4K常用 1=大字库GBK 2=Full;缺省 0) */ int background = 0; /* M4:app.json background=true(可注册无头后台服务) */ int bg_audio = 0; /* M4:app.json background_audio=true(播放中退出转后台保活) */ + int bg_keepalive = 0; /* 批①:app.json background_keepalive=true(进入后台即保活,不依赖媒体会话) */ struct app tmp; memset(&tmp, 0, sizeof tmp); while (fgets(buf, sizeof buf, f)) { @@ -779,6 +799,17 @@ void meow_parse_app_json(FILE *f, struct app *a) bg_audio = 1; } } + /* "background_keepalive":true(批①:进入后台即保活,不依赖媒体会话=通用常驻)。 + * 注意 "background_keepalive" 是 "background" 的前缀:若本行先命中上方 background 分支, + * json_str(buf,"background") 读出的是 "_keepalive…" ≠ true → 自然留 0,无害; + * json_str 键前缀独立性(util.c:544 ident_ch)保证 "background"/"background_audio" 独立匹配不受影响。 */ + if (!bg_keepalive && strstr(buf, "background_keepalive")) { + char v[8] = ""; + if (json_str(buf, "background_keepalive", v, sizeof v) > 0) { + if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes")) + bg_keepalive = 1; + } + } /* service.=mode:restart:max:window_ms(M1 解析存储,保活 M4) */ char *sp = strstr(buf, "service."); if (sp && tmp.nsvc < MEOW_MAX_SVCS) { @@ -833,6 +864,7 @@ void meow_parse_app_json(FILE *f, struct app *a) a->fonts = (char)(fonts < 0 ? 0 : (fonts > 2 ? 2 : fonts)); /* 注入 MEOW_FONT_TIER env */ a->background = background ? '1' : 0; /* M4:后台服务注册权限 */ a->background_audio = bg_audio ? '1' : 0; /* M4:后台播放保活 */ + a->background_keepalive = bg_keepalive ? '1' : 0; /* 批①:后台即保活(通用常驻) */ strncpy(a->icon, icon, 128); a->nroute = tmp.nroute; if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute); @@ -1551,6 +1583,7 @@ static int launch_app_chain(struct app *a, const char *chain) unlink(NOTIF_ACTIVE); drain_input(); meow_nav_begin(a->id, NULL); /* 转后台时导航栈已清;回前台从裸态重建 */ + ams_report_fg(a->id); /* 批①:单向 best-effort 上报 amsd 置 AMS_FG */ } if (!resumed) { /* 二进制依赖启动前检查(app.json depends):required 缺失阻断启动,optional 缺失放行。 @@ -1931,14 +1964,17 @@ static int launch_app_chain(struct app *a, const char *chain) break; } if (meow_nav_exit_pending()) { - /* M4 后台播放保活:声明 background_audio 且该 App 媒体会话 PLAYING → 不终止, - * 进程+音频流保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**—— - * SIGSTOP 会冻结同进程播放线程断供 PCM,§10.5 真机实证)。登记失败 - *(槽满/已存在)回落下方真退序列。卡拔例外不适用保活(bg 表 tick 照杀)。 */ + /* 批①后台保活:声明 background_keepalive → 恒保活;或声明 background_audio 且该 App + * 媒体会话活跃(PLAYING/PAUSED/BUFFER,见 meow_media_app_resident)→ 保活。进程+音频流 + * 保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**——SIGSTOP 会冻结同进程 + * 播放线程断供 PCM,§10.5 真机实证)。登记失败(槽满/已存在)回落下方真退序列。 + * 卡拔例外不适用保活(bg 表 tick 照杀)。 */ if (meow_bg_should_keep(a->background_audio == '1', - meow_media_app_playing(a->id)) && - meow_bg_add(a->id, pid, out_fd, app_on_card) == 0) { - logmsg("bg: app %s nav-exit -> 后台保活(播放中,pid=%d 保留,SDK present 自抑)", + meow_media_app_resident(a->id), + a->background_keepalive == '1') && + meow_bg_add(a->id, pid, out_fd, app_on_card, + a->background_keepalive == '1') == 0) { + logmsg("bg: app %s nav-exit -> 后台保活(活跃(播放/暂停/缓冲),pid=%d 保留,SDK present 自抑)", a->name, (int)pid); to_background = 1; break; @@ -2177,6 +2213,7 @@ static int launch_app_chain(struct app *a, const char *chain) meow_dirty_add(0, SB_BAR_Y, fb_xres, SB_BAR_H); settings_load(); meow_nav_exit(); /* 前台会话导航栈清栈(App 本体栈内存活;回前台时裸态重建) */ + ams_report_bg(a ? a->id : "", pid); /* 批①:单向 best-effort 上报 amsd 置 AMS_BG */ return 0; } /* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */ diff --git a/patch_v2/meow_launcherd/meow_bgplay.c b/patch_v2/meow_launcherd/meow_bgplay.c index 94367bc..ca7c038 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.c +++ b/patch_v2/meow_launcherd/meow_bgplay.c @@ -63,9 +63,10 @@ void meow_bgplay_init(const meow_bgplay_ops *ops) /* ================= 后台播放保活 ================= */ -int meow_bg_should_keep(int bg_audio_declared, int media_playing) +int meow_bg_should_keep(int bg_audio_declared, int media_resident, int keepalive_declared) { - return bg_audio_declared && media_playing; + if (keepalive_declared) return 1; /* 通用常驻:进后台即保活 */ + return bg_audio_declared && media_resident; /* 播放类:播放/暂停/缓冲都保活 */ } static meow_bg_entry_t *bg_find(const char *app_id) @@ -78,7 +79,7 @@ static meow_bg_entry_t *bg_find(const char *app_id) return NULL; } -int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card) +int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive) { int i; @@ -94,8 +95,11 @@ int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card) e->pid = pid; e->out_fd = out_fd; e->on_card = on_card; + e->bg_since_ms = bg_now_ms(); /* 批①:转后台时刻 */ + e->keepalive = keepalive ? 1 : 0; /* 批①:常驻类型标记 */ g_change_seq++; - bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d", app_id, (int)pid, on_card); + bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d keepalive=%d", + app_id, (int)pid, on_card, e->keepalive); return 0; } } diff --git a/patch_v2/meow_launcherd/meow_bgplay.h b/patch_v2/meow_launcherd/meow_bgplay.h index 04b2d92..bce43dc 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.h +++ b/patch_v2/meow_launcherd/meow_bgplay.h @@ -46,6 +46,8 @@ typedef struct meow_bg_entry { int out_fd; int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */ uint64_t term_at_ms; /* 卡拔 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */ + uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms,批③管理页/闲置回收用) */ + char keepalive; /* 批①:1=background_keepalive 类(无媒体会话也保活);0=background_audio 类 */ } meow_bg_entry_t; /* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */ @@ -91,12 +93,17 @@ typedef struct meow_bgplay_ops { void meow_bgplay_init(const meow_bgplay_ops *ops); /* ---- 后台播放保活判定(纯函数,无副作用)---- */ -/* 退出时是否转后台保活:声明 background_audio 且该 App 媒体会话正在播放。 */ -int meow_bg_should_keep(int bg_audio_declared, int media_playing); +/* 退出时是否转后台保活(批①三参化): + * keepalive_declared=1(声明 background_keepalive)→ 恒保活(通用常驻,不依赖媒体会话); + * 否则 bg_audio_declared(声明 background_audio)且 media_resident(该 App 媒体会话活跃 + * =PLAYING/PAUSED/BUFFER,见 meow_media_app_resident)→ 保活。 + * 未声明/无活跃会话 → 0(走原真退序列)。 */ +int meow_bg_should_keep(int bg_audio_declared, int media_resident, int keepalive_declared); /* ---- 后台保活 App 表 ---- */ -/* 转后台:登记 app_id/pid/out_fd。返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */ -int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card); +/* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类)。 + * 返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */ +int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive); /* 回前台:按 app_id 摘除槽(进程归还前台会话)。成功返回 0 且 *out_fd(可 NULL)带回 * 管道读端;未在后台返回 -1。 */ int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid); diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 072c4cc..269b376 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -329,6 +329,7 @@ struct app { char fonts; /* 字库档:0=默认4K常用 1=大字库GBK 2=Full(app.json "fonts";注入 MEOW_FONT_TIER) */ char background; /* M4:'1'=app.json background=true(可注册无头后台服务) */ char background_audio; /* M4:'1'=background_audio=true(播放中退出转后台保活,§9) */ + char background_keepalive; /* 批①:'1'=background_keepalive=true(进入后台即保活,不依赖媒体会话=通用常驻) */ char icon[129]; /* icon= 路径 */ int nroute; /* route. 行数 */ struct meow_route routes[MEOW_MAX_ROUTES]; @@ -355,6 +356,7 @@ struct meow_acct { int declared; /* 是否经 HELLO/ACT_DECLARE 运行时声明过 */ char background; /* M4:'1'=app.json background=true(MSG_REGISTER 权限位0) */ char background_audio; /* M4:'1'=background_audio=true(MSG_REGISTER MF_PLAYBACK 权限位1) */ + char background_keepalive; /* 批①:'1'=background_keepalive=true(进入后台即保活,不依赖媒体会话) */ }; diff --git a/patch_v2/meow_launcherd/meow_media.c b/patch_v2/meow_launcherd/meow_media.c index 339bde3..63d5f54 100644 --- a/patch_v2/meow_launcherd/meow_media.c +++ b/patch_v2/meow_launcherd/meow_media.c @@ -127,6 +127,12 @@ int meow_media_app_playing(const char *app_id) return (s->state == MEOW_MEDIA_PLAYING && strcmp(s->app_id, app_id) == 0) ? 1 : 0; } +int meow_media_app_resident(const char *app_id) +{ + const meow_media_session_t *s = sess_find(app_id); + return (s && s->state != MEOW_MEDIA_STOPPED) ? 1 : 0; +} + static int send_playctl(const meow_media_session_t *s, uint32_t cmd, int64_t seek_ms) { if (!s || !s->used || !g_ops || !g_ops->send) return -1; diff --git a/patch_v2/meow_launcherd/meow_media.h b/patch_v2/meow_launcherd/meow_media.h index ac74e8b..4fbbd22 100644 --- a/patch_v2/meow_launcherd/meow_media.h +++ b/patch_v2/meow_launcherd/meow_media.h @@ -84,6 +84,11 @@ int meow_media_has_source(void); * launcher 据此决定通知中心叠加不 SIGSTOP 该 App(保 PCM 供流),SDK present 层自抑上屏。 */ int meow_media_app_playing(const char *app_id); +/* 批①后台常驻判定:该 app 的媒体会话「活跃可驻留」= 会话存在且 state != STOPPED + * (PLAYING/PAUSED/BUFFER 皆常驻)。按 app 自身会话判(sess_find),不读最近活跃源—— + * 多源时不误判「当前源≠本 App」。launcher 转后台保活依据。 */ +int meow_media_app_resident(const char *app_id); + /* 向当前源下发 PLAYCTL(卡片按钮/播放页/媒体键/控制器)。返回 0=已发,-1=无源/发送失败。 */ int meow_media_playctl(uint32_t cmd, int64_t seek_ms); diff --git a/patch_v2/meow_launcherd/test_bg_keep.c b/patch_v2/meow_launcherd/test_bg_keep.c new file mode 100644 index 0000000..ef730e5 --- /dev/null +++ b/patch_v2/meow_launcherd/test_bg_keep.c @@ -0,0 +1,161 @@ +/* test_bg_keep.c — 批①`background_keepalive` 声明层宿主单测。 + * + * 验证 meow_parse_app_json 对 app.json 后台三位(background / background_audio / + * background_keepalive)的解析与 meow_acct_sync 透传,重点覆盖**前缀不串扰**: + * - "background_keepalive":true → a->background_keepalive=='1';且 background/background_audio + * 在该行不被误命中(分别是 0/0); + * - background:true → a->background=='1'(独立键匹配不受影响); + * - background_audio:true → a->background_audio=='1'; + * - 一行三键并存:三者各归其位; + * - meow_acct_sync 透传 background_keepalive 到注册表。 + * 实现手法同 test_fg_act.c:真实 include app_launcher.c,-ffunction-sections -Wl,--gc-sections + * 只保留 parseInt/acct_sync/upsert/find 触及函数;json_str/meow_parse_depends/logmsg 由本文件提供 + * 忠实桩(json_str 复刻 util.c 语义,含键前缀独立 ident_ch + 整体 UTF-8 截断)。 + * + * 运行(patch_v2/meow_launcherd 下): + * gcc -D_GNU_SOURCE -Wall -Wextra -ffunction-sections -Wl,--gc-sections \ + * -I. -o test_bg_keep test_bg_keep.c && ./test_bg_keep + * 全 PASS -> exit 0。 + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include + +/* ---- logmsg:app_launcher.c 依赖,minimal 桩 ---- */ +void logmsg(const char *fmt, ...) +{ + (void)fmt; +} + +/* ---- json_str:复刻 util.c 语义(键前缀独立 + 值提取 + UTF-8 截断)---- */ +static int ident_ch(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '.'; +} +static void utf8_trim(char *s) +{ + size_t n = strlen(s), i = 0, last_good = 0; + while (i < n) { + unsigned char c = (unsigned char)s[i]; + int len = (c < 0x80) ? 1 : (c >= 0xF0) ? 4 : (c >= 0xE0) ? 3 : (c >= 0xC0) ? 2 : 1; + if (i + (size_t)len > n) break; + i += (size_t)len; + last_good = i; + } + s[last_good] = '\0'; +} +int json_str(const char *buf, const char *key, char *out, int olen) +{ + out[0] = 0; + if (!buf || !key || olen <= 0) return 0; + const char *p = strstr(buf, key); + if (!p) return 0; + if (p > buf && ident_ch((unsigned char)p[-1])) return 0; /* 键前缀需独立 */ + p += strlen(key); + if (*p == '"') p++; /* JSON 键自身右引号 */ + while (*p && (*p == ':' || *p == '=')) p++; + while (*p == ' ' || *p == '\t') p++; + if (*p == '"') p++; /* 值左引号 */ + int i = 0; + while (*p && *p != '"' && *p != ',' && *p != '\n' && *p != '\r' && i < olen - 1) + out[i++] = *p++; + out[i] = 0; + while (i > 0 && (out[i - 1] == ' ' || out[i - 1] == '\t')) out[--i] = 0; + utf8_trim(out); + return (int)strlen(out); +} + +#include "app_launcher.c" /* 真实实现:meow_parse_app_json / meow_acct_sync / meow_acct_upsert */ + +/* meow_parse_depends:本测试 app.json 无 depends 行,桩返回 0(struct meow_dep 此时已可见)。 */ +int meow_parse_depends(const char *val, struct meow_dep *deps, int max) +{ + (void)val; (void)deps; (void)max; + return 0; +} + +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail++; } \ + else fprintf(stderr, "ok: %s\n", msg); \ +} while (0) + +/* 用一段 app.json 内容解析出 struct app。返回 0=成功。 */ +static int parse_json(const char *content, struct app *a) +{ + FILE *f = tmpfile(); + if (!f) return -1; + fputs(content, f); + rewind(f); + memset(a, 0, sizeof *a); + meow_parse_app_json(f, a); + fclose(f); + return 0; +} + +int main(void) +{ + /* ---- 1) 单独 background_keepalive:true ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n\"background_keepalive\":true\n}", &a); + CHECK(a.background_keepalive == '1', "background_keepalive:true -> '1'"); + CHECK(a.background == 0, "keepalive 行不误命中 background (==0)"); + CHECK(a.background_audio == 0, "keepalive 行不误命中 background_audio (==0)"); + } + /* ---- 2) 单独 background:true ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n\"background\":true\n}", &a); + CHECK(a.background == '1', "background:true -> '1'(独立键匹配不受影响)"); + CHECK(a.background_keepalive == 0, "background 行不串 to keepalive (==0)"); + CHECK(a.background_audio == 0, "background 行不串 to background_audio (==0)"); + } + /* ---- 3) 单独 background_audio:true ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n\"background_audio\":true\n}", &a); + CHECK(a.background_audio == '1', "background_audio:true -> '1'"); + CHECK(a.background == 0, "background_audio 行不误命中 background (==0)"); + CHECK(a.background_keepalive == 0, "background_audio 行不串 to keepalive (==0)"); + } + /* ---- 4) 三键并存:各归其位(逐行格式,值后跟 , 或换行,同真实 app.json) ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n\"background\":true,\n" + "\"background_audio\":true,\n\"background_keepalive\":true\n}", &a); + CHECK(a.background == '1', "三键并存 background=='1'"); + CHECK(a.background_audio == '1', "三键并存 background_audio=='1'"); + CHECK(a.background_keepalive == '1', "三键并存 background_keepalive=='1'"); + } + /* ---- 5) 假值 / 前导噪声不应置位 ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n\"background_keepalive\":false,\n" + "\"background_audio\":0\n}", &a); + CHECK(a.background_keepalive == 0, "background_keepalive:false -> 0"); + CHECK(a.background_audio == 0, "background_audio:0 -> 0"); + CHECK(a.background == 0, "无 background 键 -> 0"); + } + /* ---- 6) meow_acct_sync 透传 ---- */ + { + struct app a; + parse_json("{\n\"name\":\"X\",\n\"exec\":\"/usr/bin/x\",\n" + "\"background_keepalive\":true,\n\"background_audio\":true\n}", &a); + meow_acct_sync("dev.test.keep", &a); /* 以 app_id 建/更注册表项 */ + struct meow_acct *ac = meow_acct_find("dev.test.keep"); + CHECK(ac != NULL, "meow_acct_sync 建注册表项"); + CHECK(ac && ac->background_keepalive == '1', "透传 background_keepalive=='1'"); + CHECK(ac && ac->background_audio == '1', "透传 background_audio=='1'"); + CHECK(ac && ac->background == 0, "透传 background==0"); + } + + if (g_fail) { fprintf(stderr, "\n== %d assertion(s) FAILED ==\n", g_fail); return 1; } + fprintf(stderr, "\n== test_bg_keep ALL PASS ==\n"); + return 0; +} diff --git a/patch_v2/meow_launcherd/test_bgplay.c b/patch_v2/meow_launcherd/test_bgplay.c index c33e479..ccc662b 100644 --- a/patch_v2/meow_launcherd/test_bgplay.c +++ b/patch_v2/meow_launcherd/test_bgplay.c @@ -163,11 +163,15 @@ int main(void) mops.send = fake_send_frame; meow_media_init(&mops); - /* ---- 1) should_keep 判定矩阵 ---- */ - CHECK(meow_bg_should_keep(0, 0) == 0, "should_keep(未声明,未播放)=0"); - CHECK(meow_bg_should_keep(1, 0) == 0, "should_keep(声明,未播放)=0"); - CHECK(meow_bg_should_keep(0, 1) == 0, "should_keep(未声明,播放)=0"); - CHECK(meow_bg_should_keep(1, 1) == 1, "should_keep(声明,播放)=1"); + /* ---- 1) should_keep 判定矩阵(批①三参:bg_audio, media_resident, keepalive) ---- */ + CHECK(meow_bg_should_keep(0, 0, 0) == 0, "should_keep(未声明,未活跃,非常驻)=0"); + CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(声明bg_audio,无会话)=0"); + CHECK(meow_bg_should_keep(0, 1, 0) == 0, "should_keep(未声明bg_audio,活跃)=0"); + CHECK(meow_bg_should_keep(1, 1, 0) == 1, "should_keep(bg_audio+活跃)=1"); + CHECK(meow_bg_should_keep(0, 0, 1) == 1, "should_keep(keepalive恒保)=1"); + CHECK(meow_bg_should_keep(0, 1, 1) == 1, "should_keep(keepalive恒保,忽略media)=1"); + CHECK(meow_bg_should_keep(1, 0, 1) == 1, "should_keep(keepalive恒保,无会话)=1"); + CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(bg_audio+resident=0 真退)=0"); /* ---- 2) 后台播放保活全链路 ---- */ int sp[2]; @@ -181,14 +185,20 @@ int main(void) CHECK(meow_media_meta(&meta, sp[0]) == 0, "META 上报 PLAYING 注册媒体会话"); } CHECK(meow_media_app_playing("dev.test.bg1") == 1, "app_playing(bg1)=1"); - CHECK(meow_bg_should_keep(1, meow_media_app_playing("dev.test.bg1")) == 1, + CHECK(meow_media_app_resident("dev.test.bg1") == 1, "app_resident(bg1)=1"); + CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 1, "播放中退出 → 应保活"); pid_t kid = spawn_sleeper(); CHECK(kid > 0, "fork 假 App 子进程"); - CHECK(meow_bg_add("dev.test.bg1", kid, sp[0], 0) == 0, "bg_add 转后台保活"); + CHECK(meow_bg_add("dev.test.bg1", kid, sp[0], 0, 0) == 0, "bg_add 转后台保活"); CHECK(meow_bg_is_backgrounded("dev.test.bg1") == 1, "is_backgrounded=1"); CHECK(meow_bg_count() == 1, "bg count=1"); + { + const meow_bg_entry_t *be = meow_bg_at(0); + CHECK(be && be->bg_since_ms > 0, "bg 槽记 bg_since_ms(转后台时刻)"); + CHECK(be && be->keepalive == 0, "bg_audio 类 keepalive=0"); + } CHECK(kill(kid, 0) == 0, "后台化后进程仍存活(未 SIGTERM/SIGSTOP)"); /* PLAYCTL 经会话 fd 往返(后台化不断连:进程/fd 均保留) */ CHECK(meow_media_playctl_to("dev.test.bg1", 6, 0) == 0, "PLAYCTL 下发到后台 App 会话"); @@ -215,16 +225,28 @@ int main(void) meta.state = MEOW_MEDIA_STOPPED; meow_media_meta(&meta, sp[0]); } - CHECK(meow_bg_should_keep(1, meow_media_app_playing("dev.test.bg1")) == 0, + CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 0, "停播后再退出 → should_keep=0(真退 SIGTERM)"); kill(kid, SIGKILL); waitpid(kid, NULL, 0); close(sp[0]); close(sp[1]); meow_media_clear("dev.test.bg1"); + /* ---- 2b) keepalive 类(background_keepalive)入槽标记 ---- */ + { + pid_t kk = spawn_sleeper(); + CHECK(meow_bg_add("dev.test.keep", kk, -1, 0, 1) == 0, "bg_add(keepalive=1)"); + const meow_bg_entry_t *bk = meow_bg_at(0); + CHECK(bk && bk->keepalive == 1, "keepalive 类槽 keepalive=1"); + CHECK(bk && bk->bg_since_ms > 0, "keepalive 类槽 bg_since_ms>0"); + meow_bg_take("dev.test.keep", NULL, NULL); + kill(kk, SIGKILL); waitpid(kk, NULL, 0); + CHECK(meow_bg_count() == 0, "keepalive 槽 take 后清槽"); + } + /* ---- 3) 后台 App 崩溃照收 ---- */ g_bg_dead = 0; kid = spawn_sleeper(); - CHECK(meow_bg_add("dev.test.crash", kid, -1, 0) == 0, "bg_add(crash)"); + CHECK(meow_bg_add("dev.test.crash", kid, -1, 0, 0) == 0, "bg_add(crash)"); kill(kid, SIGKILL); { int ok = pump_bg_until(cond_bg_dead, NULL); @@ -238,7 +260,7 @@ int main(void) /* ---- 4) 卡拔例外:on_card=1 + 卡不在 → SIGTERM 回收 ---- */ g_bg_dead = 0; kid = spawn_sleeper(); - CHECK(meow_bg_add("dev.test.card", kid, -1, 1) == 0, "bg_add(on_card=1)"); + CHECK(meow_bg_add("dev.test.card", kid, -1, 1, 0) == 0, "bg_add(on_card=1)"); g_card = 0; pump_bg_until(cond_bg_dead, NULL); CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.card") == 0, @@ -253,18 +275,18 @@ int main(void) for (i = 0; i < MEOW_BG_MAX; i++) { k[i] = spawn_sleeper(); snprintf(id, sizeof id, "dev.test.full%d", i); - meow_bg_add(id, k[i], -1, 0); + meow_bg_add(id, k[i], -1, 0, 0); } snprintf(id, sizeof id, "dev.test.full%d", MEOW_BG_MAX); { pid_t extra = spawn_sleeper(); - full = (meow_bg_add(id, extra, -1, 0) == MEOW_ERR_NOMEM); + full = (meow_bg_add(id, extra, -1, 0, 0) == MEOW_ERR_NOMEM); kill(extra, SIGKILL); waitpid(extra, NULL, 0); /* 未入表,就地清理 */ } CHECK(full, "bg 表满 → MEOW_ERR_NOMEM"); snprintf(id, sizeof id, "dev.test.full%d", 0); - CHECK(meow_bg_add(id, 0, -1, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL"); - CHECK(meow_bg_add(id, k[0], -1, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS"); + CHECK(meow_bg_add(id, 0, -1, 0, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL"); + CHECK(meow_bg_add(id, k[0], -1, 0, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS"); for (i = 0; i < MEOW_BG_MAX; i++) { snprintf(id, sizeof id, "dev.test.full%d", i); meow_bg_take(id, NULL, NULL); @@ -356,7 +378,7 @@ int main(void) int spid = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH, 1, 3, 60000, 0, sp2[0]); pid_t bgk = spawn_sleeper(); - meow_bg_add("dev.test.bg9", bgk, -1, 0); + meow_bg_add("dev.test.bg9", bgk, -1, 0, 0); CHECK(spid > 0 && meow_bg_count() == 1, "kill_all 前置:1 svc + 1 bg"); meow_bgplay_kill_all(); CHECK(meow_bg_count() == 0 && meow_svc_find("dev.test.svc", "daemon") == NULL, diff --git a/patch_v2/meow_launcherd/test_media.c b/patch_v2/meow_launcherd/test_media.c index f58b5e5..a41006f 100644 --- a/patch_v2/meow_launcherd/test_media.c +++ b/patch_v2/meow_launcherd/test_media.c @@ -191,6 +191,25 @@ int main(void) CHECK(meow_media_app_playing("com.ncm") == 0, "app_playing: not current source == 0"); meow_media_clear(NULL); + /* 9) 批①后台常驻判定 meow_media_app_resident(launcher 转后台保活判据:会话存在且 state≠STOPPED) */ + CHECK(meow_media_app_resident("com.ncm") == 0, "resident: no source == 0"); + fill_meta(&m, "com.ncm", MEOW_MEDIA_PLAYING, 1000, 240000, "Song A", "Artist A"); + meow_media_meta(&m, sv[0]); + CHECK(meow_media_app_resident("com.ncm") == 1, "resident: PLAYING == 1"); + CHECK(meow_media_app_resident("com.other") == 0, "resident: other app (no session) == 0"); + fill_meta(&m, "com.ncm", MEOW_MEDIA_PAUSED, 1000, 240000, "Song A", "Artist A"); + meow_media_meta(&m, sv[0]); + CHECK(meow_media_app_resident("com.ncm") == 1, "resident: PAUSED == 1 (暂停也常驻)"); + fill_meta(&m, "com.ncm", MEOW_MEDIA_BUFFER, 1000, 240000, "Song A", "Artist A"); + meow_media_meta(&m, sv[0]); + CHECK(meow_media_app_resident("com.ncm") == 1, "resident: BUFFER == 1"); + fill_meta(&m, "com.ncm", MEOW_MEDIA_STOPPED, 0, 0, "Song A", "Artist A"); + meow_media_meta(&m, sv[0]); + CHECK(meow_media_app_resident("com.ncm") == 0, "resident: STOPPED == 0"); + CHECK(meow_media_app_resident("") == 0, "resident: empty app_id == 0"); + CHECK(meow_media_app_resident(NULL) == 0, "resident: NULL app_id == 0"); + meow_media_clear(NULL); + printf("\n%s (%d fails)\n", g_fails ? "FAILED" : "ALL PASS", g_fails); return g_fails ? 1 : 0; }