feat(bt): 批①后台常驻框架实施 — meow_media_app_resident按app会话判活跃+should_keep三参(keepalive||bg_audio&&resident)+background_keepalive声明解析透传+amsd EV BG/FG上报;宿主test_media39/test_bgplay/test_bg_keep19全过,0warning;落盘APP_BACKGROUND_KEEPALIVE_P1;降配(nice)待真机未做
This commit is contained in:
1 parent
7e68313981
commit
f3ae06d7cd
10 files changed
+330
-30
No files matched your search
@@ -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 <app> <pid> → AMS_BG(转后台保活)
|
||||
* EV FG <app> → 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <app> <pid>`、回前台 `EV FG <app>`。
|
||||
* 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.<svc>=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) */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.<id> 行数 */
|
||||
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(进入后台即保活,不依赖媒体会话) */
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ---- 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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user