fix(launcher/amsd): amsd MSG_AMS_START 拉起缺口——pending-start 抽 meow_ams_start 模块复用启动链 + 负错误码符修复

根因(真机日志实证,M2.92 遗留):meow_ams_pending_start_set 误返回正错误码
(-MEOW_ERR_BUSY=+8/-MEOW_ERR_INVAL=+1),ipc_dispatch 的 rc<0?-rc:MEOW_OK 把「有前台 App
拒绝/空包名非法」误判为 OK(0) → amsd 报 OK 但 pending 未置位 → App 永不拉起(am start 报 OK
但界面没出现/进程未起)。

launcher:
- 新增自包含模块 meow_ams_start.c/.h(ops 注入 launch=launch_app/launch_activity、
  resolve=resolve_chain、log=logmsg):pending 状态 + 拉起决策 + picker 上下文。
- set 统一返回负 MEOW_ERR_*(根因修复);consume 复用既有启动链路真正拉起目标 App,
  列表未就绪(state!=0)保留 pending 待 2s 重扫重试,不再静默丢弃。
- main_menu run_menu 改经 meow_ams_start_consume 消费(保留 launch_fail_toast/菜单收尾);
  app_launcher 移除旧 g_ams_start/g_ams_pick_* 迁入模块;util meow_ipc_process 顶部同步 busy、
  meow_ipc_init 末尾 meow_ams_launcher_init()。
- build_launcher.sh 编入 meow_ams_start.c;meow_launcher_internal.h 更新挂点声明。

amsd(协议层归属说明):cmd_start 的 busy 判定对照 launcher ACK 正幅度(busy=8),
原 (uint32_t)MEOW_ERR_BUSY=0xFFFFFFF8 永不匹配 → busy 落入 launcher offline,改 (uint32_t)(-MEOW_ERR_BUSY)。

验证:宿主 test_ams_start.c 33 断言全 PASS(合法->拉起链、activity chain 透传、空包名/非法/busy
不拉起不崩、列表未就绪保留 pending、无路由 NO ROUTE、启动失败 idx 回报、picker 取回);
launcher 目录全量回归 test_media/bgplay/depends/notif_panel/notif_tf/settings_brightness/
vol_ramp_jack/host_icon + test_font_tier/weight 全 PASS;mipsel -Wall -Wextra 0 warning。
新 md5:meow_launcherd a8200ae1b0aa68d7262749db87377d49、meow_amsd 7ddc677ce7938bc9b95c83c17bc78295。(M2.98 待打包,未刷机)
This commit is contained in:
MeowOS committed 2026-09-01 19:40:29 +08:00
1 parent 7e7e45ae26
commit 980d161972
13 files changed
+446 -101

No files matched your search

Binary file not shown.
+2 -1
View File
@@ -1171,7 +1171,8 @@ static int cmd_start(const char *spec, char *resp, int rn)
hist_push(a, "ams_start", "intent (forwarded)", (long)time(NULL), read_uptime_sec()); hist_push(a, "ams_start", "intent (forwarded)", (long)time(NULL), read_uptime_sec());
snprintf(detail, sizeof detail, "ok forwarded"); snprintf(detail, sizeof detail, "ok forwarded");
emit_log("AMS start %s%s forwarded to launcher", pkg, act[0] ? "/" : ""); emit_log("AMS start %s%s forwarded to launcher", pkg, act[0] ? "/" : "");
} else if (rt == 0 && code == (uint32_t)MEOW_ERR_BUSY) { } else if (rt == 0 && code == (uint32_t)(-MEOW_ERR_BUSY)) {
/* launcher ACK ack.code 携带正幅度(busy=8,见 util.c `rc<0?-rc:MEOW_OK`),非二补。 */
snprintf(detail, sizeof detail, "busy (app running)"); snprintf(detail, sizeof detail, "busy (app running)");
emit_log("AMS start %s -> BUSY (app running)", pkg); emit_log("AMS start %s -> BUSY (app running)", pkg);
} else { } else {
Binary file not shown.
+36 -56
View File
@@ -1,6 +1,7 @@
/* app_launcher.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/ /* app_launcher.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/ /* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h" #include "meow_launcher_internal.h"
#include "meow_ams_start.h" /* MSG_AMS_START pending/拉起决策(meow_ams_start.c) */
static struct meow_acct g_accts[MEOW_MAX_ACTS]; /* 主循环单写者 */ static struct meow_acct g_accts[MEOW_MAX_ACTS]; /* 主循环单写者 */
@@ -201,30 +202,9 @@ static int reap_wait(pid_t pid, int *status)
} }
} }
/* ---- amsd start/kill 挂点(主循环单写者)---- */ /* ---- amsd kill 挂点(主循环单写者;start 侧见 meow_ams_start.c)---- */
static struct { char pkg[MEOW_APP_ID_MAX]; char act[MEOW_ACT_MAX]; int pending; } g_ams_start;
static struct { char pkg[MEOW_APP_ID_MAX]; int pending; } g_ams_kill; static struct { char pkg[MEOW_APP_ID_MAX]; int pending; } g_ams_kill;
int meow_ams_pending_start_set(const char *pkg, const char *act)
{
if (!pkg || !pkg[0]) return -MEOW_ERR_INVAL;
if (g_app_session_active) return -MEOW_ERR_BUSY;
snprintf(g_ams_start.pkg, sizeof g_ams_start.pkg, "%s", pkg);
g_ams_start.act[0] = 0;
if (act && act[0]) snprintf(g_ams_start.act, sizeof g_ams_start.act, "%s", act);
g_ams_start.pending = 1;
return 0;
}
int meow_ams_take_start(char *pkg, int pn, char *act, int an)
{
if (!g_ams_start.pending) return 0;
g_ams_start.pending = 0;
if (pkg && pn > 0) snprintf(pkg, pn, "%s", g_ams_start.pkg);
if (act && an > 0) snprintf(act, an, "%s", g_ams_start.act);
return 1;
}
void meow_ams_pending_kill_set(const char *pkg) void meow_ams_pending_kill_set(const char *pkg)
{ {
if (!pkg || !pkg[0]) return; if (!pkg || !pkg[0]) return;
@@ -241,34 +221,38 @@ int meow_ams_kill_pending(const char *pkg)
return 1; return 1;
} }
/* ---- AMSD_DESIGN §6.2 二期:picker 上下文(随 pending-start 置位,launch_app_chain 消费后清)---- /* ---- amsd-start ops 组装(launch_app/launch_activity + resolve_chain + logmsg)----
* amsd `am picker` → MSG_AMS_START 扩载荷(picker=1)→ ipc_dispatch 经 set_ex 置位 + 武装 * meow_ams_start_consume 经 ops.launch 复用既有启动链路真正拉起 App(chain 空=裸启动、
* g_ams_pick_armed;main_menu 消费 pending-start(照旧 launch_app/launch_activity)时, * 非空=activity 链)。ops.launch 的 idx→struct app 映射经 g_ams_ctx 完成(run_menu 每次
* launch_app_chain 读到武装位 → 注入 MEOW_PICKER_ACTION/REQ_ID/CALLER 到子进程 → 解除武装。 * 消费前 meow_ams_ctx_set(apps, napps))。 */
* 单一写者(ipc_dispatch / launch_app_chain),且同一时刻仅一个 pending-start 在飞(v1 单前台 App), static struct app *g_ams_ctx_apps;
* 故武装位不会错配到非 picker 启动。 */ static int g_ams_ctx_napps;
static char g_ams_pick_action[24];
static char g_ams_pick_caller[MEOW_APP_ID_MAX];
static uint32_t g_ams_pick_req;
static int g_ams_pick_armed;
int meow_ams_pending_start_set_ex(const char *pkg, const char *act, void meow_ams_ctx_set(struct app *apps, int napps)
const char *pick_action, uint32_t pick_req,
const char *pick_caller)
{ {
int rc = meow_ams_pending_start_set(pkg, act); g_ams_ctx_apps = apps;
if (rc != 0) return rc; /* 拒绝(有前台 App / 参数非法) */ g_ams_ctx_napps = napps;
g_ams_pick_action[0] = 0; }
g_ams_pick_caller[0] = 0;
g_ams_pick_req = 0; static int ams_launch_cb(int idx, const char *chain)
g_ams_pick_armed = 0; {
if (pick_action && pick_action[0]) { if (!g_ams_ctx_apps || idx < 0 || idx >= g_ams_ctx_napps) return -MEOW_ERR_NOENT;
snprintf(g_ams_pick_action, sizeof g_ams_pick_action, "%s", pick_action); if (chain && chain[0]) return launch_activity(&g_ams_ctx_apps[idx], chain);
if (pick_caller) snprintf(g_ams_pick_caller, sizeof g_ams_pick_caller, "%s", pick_caller); return launch_app(&g_ams_ctx_apps[idx]);
g_ams_pick_req = pick_req; }
g_ams_pick_armed = 1;
} static int ams_resolve_cb(const char *app_id, const char *activity, char *chain, int *depth)
return 0; {
return resolve_chain(app_id, activity, chain, depth);
}
void meow_ams_launcher_init(void)
{
static struct meow_ams_ops ops;
ops.launch = ams_launch_cb;
ops.resolve = ams_resolve_cb;
ops.log = logmsg;
meow_ams_ops_init(&ops);
} }
/* 子进程 exec 前注入 picker env(action/req/caller 已由 launch_app_chain 入口捕获并解除全局武装, /* 子进程 exec 前注入 picker env(action/req/caller 已由 launch_app_chain 入口捕获并解除全局武装,
@@ -1452,17 +1436,13 @@ static int run_app_notif_overlay(struct app *a, pid_t pid)
static int launch_app_chain(struct app *a, const char *chain) static int launch_app_chain(struct app *a, const char *chain)
{ {
logmsg("launch: %s (%s) chain=\"%s\"", a->name, a->exec, chain ? chain : ""); logmsg("launch: %s (%s) chain=\"%s\"", a->name, a->exec, chain ? chain : "");
/* §6.2 二期:入口即捕获 picker 上下文并解除全局武装——即便后续 exec 失败(!a->ok)返回, /* §6.2 二期:入口即捕获 picker 上下文并解除武装(meow_ams_take_picker)——即便后续
* 武装位也已清,不会错配到下一次普通启动(单飞不泄漏)。 */ * exec 失败(!a->ok)返回,武装位也已清,不会错配到下一次普通启动(单飞不泄漏)。 */
char pick_action[24] = ""; char pick_action[24] = "";
char pick_caller[MEOW_APP_ID_MAX] = ""; char pick_caller[MEOW_APP_ID_MAX] = "";
uint32_t pick_req = 0; uint32_t pick_req = 0;
if (g_ams_pick_armed) { meow_ams_take_picker(pick_action, sizeof pick_action,
snprintf(pick_action, sizeof pick_action, "%s", g_ams_pick_action); pick_caller, sizeof pick_caller, &pick_req);
snprintf(pick_caller, sizeof pick_caller, "%s", g_ams_pick_caller);
pick_req = g_ams_pick_req;
g_ams_pick_armed = 0;
}
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; } if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
meow_dep_clear_reason(); /* 每次 launch 入口清 stale 依赖阻断原因(防上一 app 残留) */ meow_dep_clear_reason(); /* 每次 launch 入口清 stale 依赖阻断原因(防上一 app 残留) */
@@ -28,6 +28,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
subpage.c wifi_page.c bt_page.c now_playing.c \ subpage.c wifi_page.c bt_page.c now_playing.c \
font_theme.c keys.c util.c meow_dep.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_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
meow_ams_start.c \
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd "../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
echo "== meow_launcherd ==" echo "== meow_launcherd =="
ls -la meow_launcherd ls -la meow_launcherd
+20 -34
View File
@@ -1,6 +1,7 @@
/* main_menu.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/ /* main_menu.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/ /* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h" #include "meow_launcher_internal.h"
#include "meow_ams_start.h" /* MSG_AMS_START 拉起决策(meow_ams_start.c):run_menu 消费用 */
/* set by run_menu: the player's panel was blanked (screen timeout) when the /* set by run_menu: the player's panel was blanked (screen timeout) when the
@@ -885,43 +886,28 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
/* 阶段 D · M0:菜单/主屏循环内排空 IPC(非阻塞;应用可随时查询/声明路由) */ /* 阶段 D · M0:菜单/主屏循环内排空 IPC(非阻塞;应用可随时查询/声明路由) */
meow_ipc_process(); meow_ipc_process();
/* AMSD_DESIGN §4.3:amsd `am start` → 消费 pending-start(仅菜单态/无前台 App 时拉起)。 /* AMSD_DESIGN §4.3:amsd `am start` → meow_ams_start_consume 消费 pending-start,
* 复用既有启动链路(launch_app/launch_activity)真正拉起目标 App。
* 镜像 sim-tap 启动路径:launch 后 menu_after_app_exit + dirty 重绘菜单。 */ * 镜像 sim-tap 启动路径:launch 后 menu_after_app_exit + dirty 重绘菜单。 */
{ {
char spkg[MEOW_APP_ID_MAX], sact[MEOW_ACT_MAX]; const char *ids[MAX_APPS];
if (meow_ams_take_start(spkg, sizeof spkg, sact, sizeof sact)) { for (int i = 0; i < napps; i++) ids[i] = apps[i].id;
if (state != 0) { meow_ams_ctx_set(apps, napps); /* ops.launch 的 idx→struct app 映射 */
snprintf(toast, sizeof toast, "AMS START: app list not ready"); int app_ran = 0, idx = -1;
toast_ms = TOAST_MS; int r = meow_ams_start_consume(ids, napps, state, toast, sizeof toast,
} else { &app_ran, &idx);
int idx = -1; if (r == 1) {
for (int i = 0; i < napps; i++) if (!app_ran && idx >= 0 && idx < napps) {
if (!strcmp(apps[i].id, spkg)) { idx = i; break; } /* 启动失败:复用 run_menu 的失败 toast(缺依赖/exec 失败,含 app 名)。 */
if (idx < 0) { launch_fail_toast(toast, sizeof toast, apps[idx].name);
snprintf(toast, sizeof toast, "AMS START: NO APP %s", spkg);
toast_ms = TOAST_MS;
} else {
int ret;
if (sact[0]) {
char chain[MEOW_CHAIN_MAX]; int depth;
if (resolve_chain(spkg, sact, chain, &depth) == MEOW_OK)
ret = launch_activity(&apps[idx], chain);
else {
snprintf(toast, sizeof toast, "AMS: NO ROUTE %s/%s", spkg, sact);
toast_ms = TOAST_MS; ret = 0;
}
} else {
ret = launch_app(&apps[idx]);
}
if (ret != 0) {
launch_fail_toast(toast, sizeof toast, apps[idx].name);
toast_ms = TOAST_MS;
}
menu_after_app_exit(ret == 0);
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入 */
dirty = 1;
}
} }
toast_ms = TOAST_MS;
menu_after_app_exit(app_ran);
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入 */
dirty = 1;
} else if (r == 2) {
/* 应用列表未就绪:pending 保留(下次重扫即拉起),仅提示,不红冲免忙循环。 */
toast_ms = TOAST_MS;
} }
} }
meow_notif_tick(); /* M2a:通知 TTL 10s 转储(§8)*/ meow_notif_tick(); /* M2a:通知 TTL 10s 转储(§8)*/
+129
View File
@@ -0,0 +1,129 @@
/* meow_ams_start.c — MSG_AMS_START 拉起决策(AMSD_DESIGN §4.3)。
*
* 独立模块(无 launcher 内部依赖,仅 meow_ipc.h):持有 pending-start 状态 + 拉起决策,
* 经 ops 注入真实启动链路(launch_app/launch_activity + resolve_chain + logmsg),
* 使「am start」在 run_menu 主循环中真正拉起目标 App。
*
* 根因修复:历史实现 `meow_ams_pending_start_set` 返回 `-MEOW_ERR_BUSY`(=+8,正)而非
* 负错误码,ipc_dispatch 的 `rc<0?-rc:MEOW_OK` 因此把「有前台 App 拒绝」误判为 OK(0),
* 且拒绝发生在置 pending 之前 → amsd 报 OK 但 App 永不拉起。本模块统一为负 MEOW_ERR_*。
*
* 单一写者约定:pending 状态只在主循环上下文写入/消费(ipc_dispatch 写、run_menu 消费),
* 与 launcher 「单写者」纪律一致,无需锁。
*/
#include "meow_ams_start.h"
#include <stdio.h>
#include <string.h>
struct meow_ams_start_state {
char pkg[MEOW_APP_ID_MAX];
char act[MEOW_ACT_MAX];
int pending;
/* picker 上下文(二期;pick_armed=1 时 next launch 注入 env) */
char pick_action[24];
char pick_caller[MEOW_APP_ID_MAX];
uint32_t pick_req;
int pick_armed;
};
static struct meow_ams_start_state g_ams; /* 主循环单写者,无锁 */
static struct meow_ams_ops g_ops;
static int g_busy; /* =g_app_session_active 镜像(主循环每圈同步) */
void meow_ams_ops_init(const struct meow_ams_ops *ops)
{
if (ops) g_ops = *ops;
else memset(&g_ops, 0, sizeof g_ops);
}
void meow_ams_set_busy(int busy)
{
g_busy = busy;
}
int meow_ams_pending_start_set(const char *pkg, const char *act)
{
if (!pkg || !pkg[0]) return MEOW_ERR_INVAL; /* 负错误码(根因修复) */
if (g_busy) return MEOW_ERR_BUSY; /* 有前台 App:拒绝(负错误码) */
snprintf(g_ams.pkg, sizeof g_ams.pkg, "%s", pkg);
g_ams.act[0] = 0;
if (act && act[0]) snprintf(g_ams.act, sizeof g_ams.act, "%s", act);
g_ams.pending = 1;
return 0;
}
int meow_ams_pending_start_set_ex(const char *pkg, const char *act,
const char *pick_action, uint32_t pick_req,
const char *pick_caller)
{
int rc = meow_ams_pending_start_set(pkg, act);
if (rc != 0) return rc; /* 拒绝(有前台 App / 参数非法) */
g_ams.pick_action[0] = 0;
g_ams.pick_caller[0] = 0;
g_ams.pick_req = 0;
g_ams.pick_armed = 0;
if (pick_action && pick_action[0]) {
snprintf(g_ams.pick_action, sizeof g_ams.pick_action, "%s", pick_action);
if (pick_caller) snprintf(g_ams.pick_caller, sizeof g_ams.pick_caller, "%s", pick_caller);
g_ams.pick_req = pick_req;
g_ams.pick_armed = 1;
}
return 0;
}
int meow_ams_take_picker(char *action, int action_len,
char *caller, int caller_len, uint32_t *req)
{
if (!g_ams.pick_armed) return 0;
g_ams.pick_armed = 0;
if (action && action_len > 0) snprintf(action, action_len, "%s", g_ams.pick_action);
if (caller && caller_len > 0) snprintf(caller, caller_len, "%s", g_ams.pick_caller);
if (req) *req = g_ams.pick_req;
return 1;
}
int meow_ams_start_consume(const char *const *ids, int napps, int state,
char *toast, int toast_len, int *app_ran, int *idx_out)
{
if (app_ran) *app_ran = 0;
if (idx_out) *idx_out = -1;
if (!g_ams.pending || g_busy) return 0; /* 无待拉起 / 前台 App 会话中 */
if (state != 0) {
/* 应用列表未就绪:保留 pending(不丢「am start」),等 run_menu 2s 重扫后重试。 */
if (toast && toast_len > 0) snprintf(toast, toast_len, "AMS START: app list not ready");
return 2;
}
int idx = -1;
for (int i = 0; i < napps; i++)
if (ids[i] && !strcmp(ids[i], g_ams.pkg)) { idx = i; break; }
if (idx < 0) {
g_ams.pending = 0;
if (toast && toast_len > 0) snprintf(toast, toast_len, "AMS START: NO APP %s", g_ams.pkg);
return 1;
}
char chain[MEOW_CHAIN_MAX] = "";
if (g_ams.act[0]) {
int depth = 0;
int rc = g_ops.resolve ? g_ops.resolve(g_ams.pkg, g_ams.act, chain, &depth)
: MEOW_ERR_NOENT;
if (rc != MEOW_OK) {
g_ams.pending = 0;
if (toast && toast_len > 0)
snprintf(toast, toast_len, "AMS: NO ROUTE %s/%s", g_ams.pkg, g_ams.act);
return 1;
}
}
g_ams.pending = 0;
if (idx_out) *idx_out = idx;
/* 复用既有启动链路(launch_app/launch_activity 阻塞至 app 退出或 exec 失败)
* chain 空=裸启动;非空=经 resolve_chain 的 activity 链(含参透传)。
* 启动失败(lr<0)不在此设 toast:调用方经 idx_out 拿到 apps[idx].name 打 launch_fail_toast。 */
int lr = g_ops.launch ? g_ops.launch(idx, chain[0] ? chain : NULL) : -MEOW_ERR_INTERNAL;
if (app_ran) *app_ran = (lr == 0);
return 1;
}
+62
View File
@@ -0,0 +1,62 @@
/* meow_ams_start.h — MSG_AMS_START 拉起决策(AMSD_DESIGN §4.3)。
*
* 独立模块以便宿主单测:pending 状态 + 拉起决策都在此,启动/解析/日志经 ops 注入
* (生产=launcher 既有链路 launch_app/launch_activity + resolve_chain + logmsg;
* 测试=假件,见 test_ams_start.c)。
*
* 约定(与 launcher 其余模块一致):本模块函数返回负的 MEOW_ERR_*(-1=INVAL / -8=BUSY)
* 表示拒绝,0 表示受理。ipc_dispatch 用 `a.code=(uint32_t)(rc<0?-rc:MEOW_OK)` 把负错误码
* 转成正的 ack.code(幅度);amsd 侧按 `(uint32_t)(-MEOW_ERR_BUSY)` 识别 busy。
* —— 历史上 set() 误返回 `-MEOW_ERR_BUSY`(=+8,正)导致 ipc_dispatch 把拒绝当 OK,
* 表现为「am start 报 OK 但 App 未拉起」;本模块统一为负错误码(根因修复)。
*/
#ifndef MEOW_AMS_START_H
#define MEOW_AMS_START_H
#include "meow_ipc.h" /* MEOW_APP_ID_MAX / MEOW_ACT_MAX / MEOW_CHAIN_MAX / MEOW_ERR_* */
/* 启动/解析/日志回调注入(单测=假件;生产在 meow_ipc_init 末尾组装)。 */
struct meow_ams_ops {
/* 执行启动。idx=调用方应用表下标;chain 空=裸启动。
* 复用 launcher 既有链路:launch_app(&apps[idx]) / launch_activity(&apps[idx], chain)。
* 返回 0=已受理(app 已运行至退出);<0=启动失败/未找到。 */
int (*launch)(int idx, const char *chain);
/* 解析 activity 链(resolve_chain)。返回 MEOW_OK / MEOW_ERR_NOENT。 */
int (*resolve)(const char *app_id, const char *activity, char *chain, int *depth);
/* 日志(可空=静默)。 */
void (*log)(const char *fmt, ...);
};
/* 组装 ops(launcher meow_ipc_init 末尾 / 测试 main 调用)。launch/resolve 须非 NULL。 */
void meow_ams_ops_init(const struct meow_ams_ops *ops);
/* 同步「前台 App 会话」占用位(=g_app_session_active)。
* busy=1 时 pending_start_set 拒绝(-MEOW_ERR_BUSY)且 consume 不动作。 */
void meow_ams_set_busy(int busy);
/* 置 pending(ipc_dispatch MSG_AMS_START 时调)。
* pkg 空 → -MEOW_ERR_INVAL;busy → -MEOW_ERR_BUSY;否则置位并返回 0。 */
int meow_ams_pending_start_set(const char *pkg, const char *act);
/* 二期 picker 扩载荷:置 pending + 武装 picker 上下文(pick_action 非空时,launch_app_chain
* 入口经 meow_ams_take_picker 注入 MEOW_PICKER_ACTION/REQ_ID/CALLER)。错误语义同 set。 */
int meow_ams_pending_start_set_ex(const char *pkg, const char *act,
const char *pick_action, uint32_t pick_req,
const char *pick_caller);
/* 消费 pending(各主循环排空 IPC 后 / run_menu 循环调用)。
* ids=napps 个应用主键串指针(apps[i].id);state=菜单态(0=就绪;非 0=列表未就绪不拉起,
* pending 保留待重扫重试)。toast/toast_len=错误提示槽(可空)。
* app_ran=输出:1=app 已运行;0=未运行(拒绝/失败)。idx_out=输出:本次使用的应用表下标
* (-1=未命中应用;>=0 且 app_ran=0 表示启动失败,调用方应据 apps[idx].name 打 launch_fail_toast)。
* 返回:0=无 pending 或 busy(暂不动作);1=已消费(拉起成功/拒绝并清 pending,toast 已设);
* 2=列表未就绪(pending 保留待重扫重试,toast 已设「not ready」,调用方勿红冲)。 */
int meow_ams_start_consume(const char *const *ids, int napps, int state,
char *toast, int toast_len, int *app_ran, int *idx_out);
/* 消费拾取 picker 上下文(launch_app_chain 入口调用,一次性清位)。
* 返回 1=有 picker 上下文;0=无。 */
int meow_ams_take_picker(char *action, int action_len,
char *caller, int caller_len, uint32_t *req);
#endif /* MEOW_AMS_START_H */
@@ -670,16 +670,23 @@ void meow_nav_exit(void); /* 清栈(应用退出/EXIT) */
void meow_nav_request_exit(void); /* MSG_NAV{EXIT}:置位让前台 waitpid 循环终止 app */ void meow_nav_request_exit(void); /* MSG_NAV{EXIT}:置位让前台 waitpid 循环终止 app */
int meow_nav_exit_pending(void); /* 读取并清除退出请求(前台 launch_* 循环轮询) */ int meow_nav_exit_pending(void); /* 读取并清除退出请求(前台 launch_* 循环轮询) */
/* ---- amsd 控制挂点(AMSD_DESIGN §4.3 / §6.2 二期):主循环单写者,跨模块声明 ---- /* ---- amsd 控制挂点(AMSD_DESIGN §4.3 / §6.2 二期):主循环单写者,跨模块声明 ----
* pending-start/kill 仅在主循环上下文写入与消费(ipc_dispatch 写、run_menu 前台循环/菜单循环读)。 */ * pending-start 状态/拉起决策在 meow_ams_start.c(自包含、可单测,见 meow_ams_start.h);
int meow_ams_pending_start_set(const char *pkg, const char *act); /* 有前台 App 时拒绝(返回 -MEOW_ERR_BUSY) */ * pending-kill 在 app_launcher.c。仅在主循环上下文写入与消费(ipc_dispatch 写、
int meow_ams_take_start(char *pkg, int pn, char *act, int an); /* 1=取到待拉起 App(清位);0=无 */ * run_menu 前台循环/菜单循环读)。 */
void meow_ams_pending_kill_set(const char *pkg); /* 请求终止某 pkg */ void meow_ams_pending_kill_set(const char *pkg); /* 请求终止某 pkg */
int meow_ams_kill_pending(const char *pkg); /* 匹配则清除并返回 1(前台循环轮询) */ int meow_ams_kill_pending(const char *pkg); /* 匹配则清除并返回 1(前台循环轮询) */
/* §6.2 二期:带 picker 上下文的 pending-start(amsd `am picker` 经 MSG_AMS_START 扩载荷)。 /* run_menu 消费 pending-start:复用 launch_app/launch_activity 链真正拉起目标 App。
* pick_action 非空 → launch_app_chain 注入 MEOW_PICKER_ACTION/REQ_ID/CALLER 到子进程。 */ * 返回 0=无 pending/busy;1=已消费(拉起成功/拒绝);2=列表未就绪(pending 保留待重扫)。
int meow_ams_pending_start_set_ex(const char *pkg, const char *act, * app_ran=1=app 已运行(据以设 touch 冷却);idx_out>=0 且 app_ran=0 → 启动失败,打 launch_fail_toast。 */
const char *pick_action, uint32_t pick_req, int meow_ams_start_consume(const char *const *ids, int napps, int state,
const char *pick_caller); char *toast, int toast_len, int *app_ran, int *idx_out);
/* launch_app_chain 入口拾取 picker 上下文(一次性清位)。 */
int meow_ams_take_picker(char *action, int action_len,
char *caller, int caller_len, uint32_t *req);
/* 组装 amsd-start ops(生产=launch_app/launch_activity+resolve_chain+logmsg;meow_ipc_init 末尾调)。 */
void meow_ams_launcher_init(void);
/* 供 run_menu/idle 挂当前 apps[] 上下文(ops.launch 的 idx→app 映射用)。 */
void meow_ams_ctx_set(struct app *apps, int napps);
/* §6.2 二期:amsd 上报通道(best-effort,app_launcher.c 的 ams_send 公开包装)。 /* §6.2 二期:amsd 上报通道(best-effort,app_launcher.c 的 ams_send 公开包装)。
* util.c 转发运行时 declare 与 picker call/result 给 amsd 用。 */ * util.c 转发运行时 declare 与 picker call/result 给 amsd 用。 */
void meow_ams_send(const char *payload, int len); void meow_ams_send(const char *payload, int len);
Binary file not shown.
+172
View File
@@ -0,0 +1,172 @@
/* test_ams_start.c — 宿主单测:MSG_AMS_START 拉起决策(meow_ams_start.c)。
*
* 覆盖(AMSD_DESIGN §4.3 缺口回归):
* 1) 合法 MSG_AMS_START(裸启动 act 空)→ 置 pending → consume → ops.launch(idx, NULL);
* 2) 合法 MSG_AMS_START + activity → resolve → consume → ops.launch(idx, chain);
* 3) 参数非法(pkg 空)→ meow_ams_pending_start_set 返回 MEOW_ERR_INVAL,不置位、不拉起、不崩;
* 4) 有前台 App(busy)→ set 返回 MEOW_ERR_BUSY(负错误码,根因修复),不拉起;
* 5) 列表未就绪(state!=0)→ consume 返回 2(pending 保留),不拉起;后就绪再 consume → 拉起;
* 6) pkg 不在应用表 → consume 返回 1,toast「NO APP」,不拉起;
* 7) activity 无路由 → consume 返回 1,toast「NO ROUTE」,不拉起;
* 8) 启动失败(ops.launch 返 <0)→ consume 返回 1,app_ran=0,idx_out 命中(供 launch_fail_toast);
* 9) picker 扩载荷:set_ex 武装 → take_picker 取回并清位。
* 只依赖 meow_ams_start.c + meow_ipc.h(纯 C),宿主 gcc 直编:
* gcc -Wall -Wextra -std=gnu99 test_ams_start.c meow_ams_start.c -o test_ams_start
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "meow_ams_start.h"
#include "meow_ipc.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)
/* ---- 假件 ---- */
static int g_launch_calls = 0; /* ops.launch 被调次数 */
static int g_launch_last_idx = -1;
static char g_launch_last_chain[MEOW_CHAIN_MAX];
static int g_launch_ret = 0; /* 可注入:0=成功 / <0=启动失败 */
static int g_resolve_calls = 0;
static int g_resolve_rc = MEOW_OK; /* 可注入:MEOW_OK / MEOW_ERR_NOENT */
static int g_log_calls = 0;
static int fake_launch(int idx, const char *chain)
{
g_launch_calls++;
g_launch_last_idx = idx;
if (chain) snprintf(g_launch_last_chain, sizeof g_launch_last_chain, "%s", chain);
else g_launch_last_chain[0] = 0;
return g_launch_ret;
}
static int fake_resolve(const char *app_id, const char *activity,
char *chain, int *depth)
{
(void)app_id;
g_resolve_calls++;
if (g_resolve_rc != MEOW_OK) return g_resolve_rc;
snprintf(chain, MEOW_CHAIN_MAX, "%s", activity); /* 简化:chain=activity */
if (depth) *depth = 1;
return MEOW_OK;
}
static void fake_log(const char *fmt, ...) { (void)fmt; g_log_calls++; }
static void reset_fakes(void)
{
g_launch_calls = 0; g_launch_last_idx = -1;
g_launch_last_chain[0] = 0; g_launch_ret = 0;
g_resolve_calls = 0; g_resolve_rc = MEOW_OK;
g_log_calls = 0;
meow_ams_set_busy(0);
}
/* 应用表:id 主键串 + 用于 idx 断言 */
static const char *g_ids[3] = { "dev.player.ncm", "os.meow.files", "os.meow.hello" };
int main(void)
{
static struct meow_ams_ops ops;
ops.launch = fake_launch;
ops.resolve = fake_resolve;
ops.log = fake_log;
meow_ams_ops_init(&ops);
char toast[64];
int app_ran = 0, idx = -1;
/* ---- 1) 合法裸启动(act 空)→ consume → ops.launch ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set("dev.player.ncm", "") == 0, "set(bare): 受理");
int r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_launch_calls == 1, "consume(bare): 拉起链路被调");
CHECK(g_launch_last_idx == 0, "consume(bare): idx 命中 dev.player.ncm");
CHECK(g_launch_last_chain[0] == 0, "consume(bare): chain 空(裸启动)");
CHECK(app_ran == 1, "consume(bare): app_ran=1");
/* ---- 2) activity 链 → resolve → consume → ops.launch(chain) ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set("os.meow.files", "pick") == 0, "set(act): 受理");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_resolve_calls == 1, "consume(act): resolve 被调");
CHECK(g_launch_calls == 1 && g_launch_last_idx == 1, "consume(act): ops.launch(idx=1)");
CHECK(g_launch_last_chain[0] && strcmp(g_launch_last_chain, "pick") == 0,
"consume(act): chain 透传 pick");
/* ---- 3) 参数非法(pkg 空)→ INVAL,不置位、不拉起 ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set("", "") == MEOW_ERR_INVAL, "set(空 pkg): 返回负错误码 MEOW_ERR_INVAL");
CHECK(meow_ams_pending_start_set(NULL, NULL) == MEOW_ERR_INVAL, "set(NULL pkg): MEOW_ERR_INVAL");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 0 && g_launch_calls == 0, "consume: 空 pkg 未置位 → 不拉起");
/* ---- 4) busy(有前台 App)→ BUSY,不拉起 ---- */
reset_fakes();
meow_ams_set_busy(1);
CHECK(meow_ams_pending_start_set("os.meow.hello", "") == MEOW_ERR_BUSY,
"set(busy): 返回负错误码 MEOW_ERR_BUSY(根因修复:旧实现返回 +8 被误判 OK)");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 0 && g_launch_calls == 0, "consume(busy): 不拉起");
meow_ams_set_busy(0);
/* ---- 5) 列表未就绪(state!=0)→ 保留 pending,后就绪拉起 ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set("dev.player.ncm", "") == 0, "set: 受理");
r = meow_ams_start_consume(g_ids, 3, 2, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 2 && g_launch_calls == 0, "consume(state!=0): 返回 2 保留 pending,不拉起");
CHECK(toast[0] && strstr(toast, "not ready"), "consume(state!=0): toast=列表未就绪");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_launch_calls == 1 && g_launch_last_idx == 0,
"consume(后就绪): pending 保留后拉起成功");
/* ---- 6) pkg 不在应用表 → NO APP,不拉起 ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set("no.such.app", "") == 0, "set(未知app): 受理");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_launch_calls == 0, "consume(未知app): 不拉起");
CHECK(toast[0] && strstr(toast, "NO APP"), "consume(未知app): toast=NO APP");
/* ---- 7) activity 无路由 → NO ROUTE,不拉起 ---- */
reset_fakes();
g_resolve_rc = MEOW_ERR_NOENT;
CHECK(meow_ams_pending_start_set("os.meow.files", "nope") == 0, "set(nope): 受理");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_launch_calls == 0, "consume(无路由): 不拉起");
CHECK(toast[0] && strstr(toast, "NO ROUTE"), "consume(无路由): toast=NO ROUTE");
g_resolve_rc = MEOW_OK;
/* ---- 8) 启动失败(ops.launch<0)→ app_ran=0,idx_out 命中 ---- */
reset_fakes();
g_launch_ret = -2; /* MEOW_ERR_NOENT */
CHECK(meow_ams_pending_start_set("dev.player.ncm", "") == 0, "set: 受理");
r = meow_ams_start_consume(g_ids, 3, 0, toast, sizeof toast, &app_ran, &idx);
CHECK(r == 1 && g_launch_calls == 1, "consume(启动失败): 拉起被调(失败)");
CHECK(app_ran == 0 && idx == 0, "consume(启动失败): app_ran=0 且 idx_out=0(供 launch_fail_toast)");
g_launch_ret = 0;
/* ---- 9) picker 扩载荷:set_ex 武装 → take_picker 取回并清位 ---- */
reset_fakes();
CHECK(meow_ams_pending_start_set_ex("os.meow.files", "pick", "pick_file", 7, "dev.player.ncm") == 0,
"set_ex(picker): 受理");
char act[24] = "", caller[MEOW_APP_ID_MAX] = "";
uint32_t req = 0;
CHECK(meow_ams_take_picker(act, sizeof act, caller, sizeof caller, &req) == 1,
"take_picker: 有上下文");
CHECK(strcmp(act, "pick_file") == 0 && req == 7 && strcmp(caller, "dev.player.ncm") == 0,
"take_picker: action/req/caller 正确");
CHECK(meow_ams_take_picker(act, sizeof act, caller, sizeof caller, &req) == 0,
"take_picker: 二次取回=0(一次性清位)");
/* 非 picker 启动:set(非 set_ex)不武装 picker */
reset_fakes();
CHECK(meow_ams_pending_start_set("dev.player.ncm", "") == 0, "set(bare): 受理");
CHECK(meow_ams_take_picker(act, sizeof act, caller, sizeof caller, &req) == 0,
"take_picker: 普通启动无 picker 上下文");
printf("\n%s (%d fails)\n", g_fails ? "FAILED" : "ALL PASS", g_fails);
return g_fails ? 1 : 0;
}
+7
View File
@@ -1,6 +1,7 @@
/* util.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/ /* util.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/ /* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h" #include "meow_launcher_internal.h"
#include "meow_ams_start.h" /* MSG_AMS_START pending/拉起决策(meow_ams_start.c):set/set_ex + busy 同步 + ops 组装 */
/* M2.x 应用日志目录简化:一次性迁移 logs/apps/<id>/logs → logs/<id>(定义见下,log_open_card 先调用)。 */ /* M2.x 应用日志目录简化:一次性迁移 logs/apps/<id>/logs → logs/<id>(定义见下,log_open_card 先调用)。 */
static void log_migrate_apps(void); static void log_migrate_apps(void);
@@ -1112,6 +1113,9 @@ int meow_ipc_init(void)
/* M4:注入后台保活/后台服务 ops(logmsg/sd_device_present/通知清理/熔断发帧)。 */ /* M4:注入后台保活/后台服务 ops(logmsg/sd_device_present/通知清理/熔断发帧)。 */
meow_bgplay_launcher_init(); meow_bgplay_launcher_init();
/* AMSD_DESIGN §4.3:注入 amsd-start ops(launch=launch_app/launch_activity,resolve=resolve_chain)。 */
meow_ams_launcher_init();
ipc_event_fd = eventfd(0, EFD_NONBLOCK); ipc_event_fd = eventfd(0, EFD_NONBLOCK);
if (ipc_event_fd < 0) if (ipc_event_fd < 0)
logmsg("ipc: eventfd fail %s (10ms poll drains anyway)", strerror(errno)); logmsg("ipc: eventfd fail %s (10ms poll drains anyway)", strerror(errno));
@@ -1130,6 +1134,9 @@ int meow_ipc_init(void)
* 事件fd 仅为低延迟优化;若为 -1,10ms 轮询仍保证每圈排空。 */ * 事件fd 仅为低延迟优化;若为 -1,10ms 轮询仍保证每圈排空。 */
void meow_ipc_process(void) void meow_ipc_process(void)
{ {
/* AMSD_DESIGN §4.3:同步「前台 App 会话」占用位 → meow_ams_pending_start_set 据此
* 在收到 MSG_AMS_START 时正确拒绝(-MEOW_ERR_BUSY),杜绝「报 OK 但未拉起」。 */
meow_ams_set_busy(g_app_session_active);
if (ipc_event_fd >= 0) { if (ipc_event_fd >= 0) {
uint64_t v; uint64_t v;
while (read(ipc_event_fd, &v, sizeof v) == (ssize_t)sizeof v) {} while (read(ipc_event_fd, &v, sizeof v) == (ssize_t)sizeof v) {}
+2 -2
View File
@@ -28,7 +28,7 @@
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)---- # ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216) patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216)
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|899ac22c10f596909d7f35ea685744de|主屏 launcher M4(后台服务保活+后台播放:MSG_REGISTER/SERVICE_STOP/MSG_SYS 退避熔断;nav-exit 播放中转后台保活,SDK present 自抑非 SIGSTOP;app.json depends 二进制依赖启动前检查:缺 required 阻断/optional 放行) patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|a8200ae1b0aa68d7262749db87377d49|主屏 launcher M4(后台服务保活+后台播放:MSG_REGISTER/SERVICE_STOP/MSG_SYS 退避熔断;nav-exit 播放中转后台保活,SDK present 自抑非 SIGSTOP;app.json depends 二进制依赖启动前检查:缺 required 阻断/optional 放行);M2.98 待打包:amsd MSG_AMS_START 拉起缺口修复(pending-start 抽 meow_ams_start 模块复用启动链真正拉起;负错误码符修复 busy/空pkg 正确拒,不再误报 OK)
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|553840cdc79d8d7bed766a5e3981ac57|应用看门狗 M2.97(音量反转 + DS1039 平方锥度 + 插孔消抖) patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|553840cdc79d8d7bed766a5e3981ac57|应用看门狗 M2.97(音量反转 + DS1039 平方锥度 + 插孔消抖)
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187) patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194) mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
@@ -46,7 +46,7 @@ patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_power|469dddb74de424f7b9bc9c1
# ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)---- # ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)----
patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移) patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)
patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|f6ded7f835dba882c17dac52b9430a45|svcd 自启 M2.97(meow_route.conf → audiod.conf + 清理旧 route conf + 预置 meow_powermgr.conf/迁移热更 exec→/usr/bin,agent-244/246/267) patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|f6ded7f835dba882c17dac52b9430a45|svcd 自启 M2.97(meow_route.conf → audiod.conf + 清理旧 route conf + 预置 meow_powermgr.conf/迁移热更 exec→/usr/bin,agent-244/246/267)
patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|da9a5004c6fdb7827c45c27daa825919|活动管理器 M2.91(注册表自愈:卡未挂载扫空 3s 快重试+30s 合并重扫+am rescan;兼容旧 launcher 5 段 EV LAUNCH;agent-250) patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|7ddc677ce7938bc9b95c83c17bc78295|活动管理器 M2.91(注册表自愈:卡未挂载扫空 3s 快重试+30s 合并重扫+am rescan;兼容旧 launcher 5 段 EV LAUNCH;agent-250);M2.98 待打包:am start busy 识别修复(对照 launcher ACK 正幅度 busy=8,bool 误判二补 0xFFFFFFF8 导致 busy 落入 launcher offline)
patch_v2/meow_amsd/am|/usr/bin/am|9e323faf1aeb0c6a6fb0425565cf525d|amsd 用户侧CLI(+resolve/picker/rescan) patch_v2/meow_amsd/am|/usr/bin/am|9e323faf1aeb0c6a6fb0425565cf525d|amsd 用户侧CLI(+resolve/picker/rescan)
patch_v2/bootmenu/S90meow_bootmenu|/etc/init.d/S90meow_bootmenu|e0ace111ce5ee446a1e3c6605ac1e9c9|bootmenu 自启 M2.85(注释同步) patch_v2/bootmenu/S90meow_bootmenu|/etc/init.d/S90meow_bootmenu|e0ace111ce5ee446a1e3c6605ac1e9c9|bootmenu 自启 M2.85(注释同步)
patch_v2/bootmenu/S92_03_start_music_player|/etc/init.d/S92_03_start_music_player|c17257aa3d93f75da72903b57cbe1034|§4.3 兜底 patch(rcS 正常路径被 S90 阻塞不执行) patch_v2/bootmenu/S92_03_start_music_player|/etc/init.d/S92_03_start_music_player|c17257aa3d93f75da72903b57cbe1034|§4.3 兜底 patch(rcS 正常路径被 S90 阻塞不执行)