- 通知中心=launcher侧系统级叠加层(§6.1③ 仅内部): meow_notif_panel.h(纯逻辑) + meow_launcherd.c(内联 draw/run_notif_center + run_menu顶部热区下滑接入) + test_notif_panel.c(56单测) - 通知中心零旁路走 meow_settings_*; 通知列表读 meow_notif; 左缘返回复用 meow_edge_back; 局部脏区; 系统级控件无公共API(App无法自建悬浮层) - meow_ui.a 零改动(d249c6e6); meow_launcherd=4652901a(0warn) - FRAMEWORK_DESIGN §10 进程模型/Supervisor树(svcd服务子进程化隔离防级联崩) ; MEOW_APP_SDK §6.1 系统UI归launcher+SDK/APP进程隔离; NOTIFICATION_SYSTEM §9 归属; SYSTEM_SETTINGS/SPEC_REF/DEVELOPMENT_PLAN 补齐 - README 重写为面向第三方(进度/架构/构建/部署/SDK开发; 固件打包标注本地工具不入库, 无敏感) - 遗留: SDK(App进程)侧通知中心/activity跳转 依赖 M3 导航栈(独立里程碑); 真机待验证
237 lines
11 KiB
C
237 lines
11 KiB
C
/* test_notif_panel.c — 宿主单测:阶段B 通知中心面板(纯逻辑 + 面板读取的存储契约)。
|
||
*
|
||
* 直接 #include "meow_notif.c" + "meow_settings.c" 于同一 TU(同 test_notif_tf.c 手法),
|
||
* 以「真实 meow_notif 存储 + 真实 meow_settings 后端」验证面板真正读取的字段契约;
|
||
* 再测 meow_notif_panel.h 的纯逻辑(滑条映射/亮度 clamp/顶部下滑判定/时间显示/行命中/滚动钳位)。
|
||
* 不依赖 launcher 其它 static / fb / 触摸。
|
||
*
|
||
* 运行:gcc -D_GNU_SOURCE -o test_notif_panel test_notif_panel.c && ./test_notif_panel
|
||
*/
|
||
#define _GNU_SOURCE
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.h>
|
||
#include <time.h>
|
||
|
||
/* stub:meow_notif.c 的 read_quota() 经 meow_settings_get_int 读 notify.* 配额。
|
||
* <0 => 键缺失 => 用默认(MEOW_NOTIF_CATS_DEF=4 / CAT_QUOTA_DEF=4 / MEDIA_QUOTA_DEF=1)。
|
||
* meow_notif.c 对 meow_settings 的唯一依赖就是这一个函数。 */
|
||
int meow_settings_get_int(const char *key, int *out)
|
||
{
|
||
(void)key; (void)out;
|
||
return -2; /* MEOW_ERR_NOENT */
|
||
}
|
||
|
||
/* 先把 tf 转储路径指向临时目录,避免污染真实卡路径(meow_notif_init 会 clear tf)*/
|
||
static char g_tflog[600];
|
||
|
||
#include "meow_notif.c" /* 真实通知存储:post/list/count/dismiss/update_progress/... */
|
||
#include "meow_notif_panel.h" /* 面板纯逻辑(header-only) */
|
||
|
||
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)
|
||
|
||
/* 收集列表到固定槽(回调式,模拟面板 ncp_collect_cb 的字段读取) */
|
||
#define MAXG 16
|
||
typedef struct { uint32_t id; char summary[96]; char body[96]; char icon[64];
|
||
char app[48]; char tgt[48]; int progress; uint8_t urgency; uint64_t ms; } gslot_t;
|
||
static gslot_t g_got[MAXG]; static int g_nslots = 0;
|
||
static int list_grab_cb(const meow_notif_t *n, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
if (g_nslots >= MAXG) return 1;
|
||
gslot_t *s = &g_got[g_nslots++];
|
||
memset(s, 0, sizeof *s);
|
||
s->id = n->id;
|
||
snprintf(s->summary, sizeof s->summary, "%s", n->summary);
|
||
snprintf(s->body, sizeof s->body, "%s", n->body);
|
||
snprintf(s->icon, sizeof s->icon, "%s", n->app_icon);
|
||
snprintf(s->app, sizeof s->app, "%s", n->app_id);
|
||
snprintf(s->tgt, sizeof s->tgt, "%s", n->target_activity);
|
||
s->progress = n->progress;
|
||
s->urgency = n->urgency;
|
||
s->ms = n->created_ms;
|
||
return 0;
|
||
}
|
||
static int grab_list(void)
|
||
{
|
||
g_nslots = 0;
|
||
meow_notif_list(NULL, list_grab_cb, NULL);
|
||
return g_nslots;
|
||
}
|
||
static gslot_t *find_slot(const char *summary)
|
||
{
|
||
for (int i = 0; i < g_nslots; i++)
|
||
if (!strcmp(g_got[i].summary, summary)) return &g_got[i];
|
||
return NULL;
|
||
}
|
||
|
||
static uint64_t wall_now_ms(void)
|
||
{
|
||
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts);
|
||
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
static void test_pure_logic(void)
|
||
{
|
||
fprintf(stderr, "--- P1 pure logic ---\n");
|
||
/* mnp_top_swipe_down */
|
||
CHECK(mnp_top_swipe_down(40, 5, 3, 90, 28, 65) == 1, "top hotzone down-dominant -> open");
|
||
CHECK(mnp_top_swipe_down(10, 5, 3, 90, 28, 65) == 0, "left-edge (down_x<24) reserved -> no open");
|
||
CHECK(mnp_top_swipe_down(40, 30, 3, 90, 28, 65) == 0, "origin below hotzone -> no open");
|
||
CHECK(mnp_top_swipe_down(40, 5, 3, 40, 28, 65) == 0, "below swipe threshold -> no open");
|
||
CHECK(mnp_top_swipe_down(40, 5, 100, 70, 28, 65) == 0, "horizontal-dominant -> no open");
|
||
CHECK(mnp_top_swipe_down(40, 5, -3, 90, 28, 65) == 1, "down with minor left drift still opens");
|
||
|
||
/* mnp_slider_val_from_x */
|
||
CHECK(mnp_slider_val_from_x(20, 20, 440, 1, 100) == 1, "slider x=left -> min");
|
||
CHECK(mnp_slider_val_from_x(460, 20, 440, 1, 100) == 100, "slider x=right -> max");
|
||
int mid = mnp_slider_val_from_x(240, 20, 440, 1, 100);
|
||
CHECK(mid >= 1 && mid <= 100, "slider x=mid within [min,max]");
|
||
CHECK(mnp_slider_val_from_x(0, 20, 440, 1, 100) == 1, "slider x out-left clamped to min");
|
||
CHECK(mnp_slider_val_from_x(999, 20, 440, 1, 100) == 100, "slider x out-right clamped to max");
|
||
|
||
/* mnp_brightness_clamp */
|
||
CHECK(mnp_brightness_clamp(0, 1) == 1, "brightness clamp below min -> min");
|
||
CHECK(mnp_brightness_clamp(150, 1) == 100, "brightness clamp above 100 -> 100");
|
||
CHECK(mnp_brightness_clamp(50, 1) == 50, "brightness 50 kept");
|
||
CHECK(mnp_brightness_clamp(3, 5) == 5, "brightness below custom min -> custom min");
|
||
CHECK(mnp_brightness_clamp(-7, -3) == 1, "brightness clamp forces vmin>=1");
|
||
|
||
/* mnp_time_ago */
|
||
uint64_t now = wall_now_ms();
|
||
char tb[32];
|
||
mnp_time_ago(now - 5000, now, tb, sizeof tb);
|
||
CHECK(!strcmp(tb, "刚刚"), "time_ago <60s -> 刚刚");
|
||
mnp_time_ago(now - 5 * 60000u, now, tb, sizeof tb);
|
||
CHECK(!strcmp(tb, "5分钟前"), "time_ago 5min -> 5分钟前");
|
||
mnp_time_ago(now - 3 * 3600000u, now, tb, sizeof tb);
|
||
CHECK(strlen(tb) == 5 && tb[2] == ':', "time_ago >1h -> HH:MM");
|
||
|
||
/* mnp_row_at */
|
||
CHECK(mnp_row_at(250, 252, 696, 92) == -1, "row_at above list -> -1");
|
||
CHECK(mnp_row_at(300, 252, 696, 92) == 0, "row_at first row -> 0");
|
||
CHECK(mnp_row_at(400, 252, 696, 92) == 1, "row_at second row -> 1");
|
||
CHECK(mnp_row_at(700, 252, 696, 92) == -1, "row_at below list -> -1");
|
||
|
||
/* mnp_scroll_clamp */
|
||
CHECK(mnp_scroll_clamp(0, 2000, 444) == 0, "scroll clamp lower -> 0");
|
||
CHECK(mnp_scroll_clamp(9999, 2000, 444) == 1556, "scroll clamp upper -> total-view");
|
||
CHECK(mnp_scroll_clamp(-5, 2000, 444) == 0, "scroll clamp negative -> 0");
|
||
CHECK(mnp_scroll_clamp(5, 400, 444) == 0, "content fits view -> 0");
|
||
}
|
||
|
||
static void test_storage_contract(void)
|
||
{
|
||
fprintf(stderr, "--- P0 storage contract (panel reads) ---\n");
|
||
/* meow_notif_init:应清内存表 + 清 tf 文件(路径已指临时);配额用默认(stub 返回 NOENT) */
|
||
CHECK(meow_notif_init() == NOTIF_OK, "meow_notif_init ok");
|
||
CHECK(meow_notif_count(NULL) == 0, "init clears memory table");
|
||
CHECK(g_max_cats == 4 && g_cat_quota == 4 && g_media_quota == 1,
|
||
"quota defaults from meow_settings backend (stub NOENT -> defaults)");
|
||
|
||
meow_notif_t n; memset(&n, 0, sizeof n);
|
||
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
|
||
snprintf(n.category, sizeof n.category, "general");
|
||
snprintf(n.summary, sizeof n.summary, "你好世界");
|
||
snprintf(n.body, sizeof n.body, "这是正文第二行");
|
||
snprintf(n.app_icon, sizeof n.app_icon, "apps/os.meow.hello/icon.png");
|
||
snprintf(n.target_activity, sizeof n.target_activity, "activity_main");
|
||
n.expire_timeout = 0; n.progress = -1; n.urgency = 1;
|
||
uint32_t id1 = 0;
|
||
CHECK(meow_notif_post(&n, &id1) == NOTIF_OK, "post general ok");
|
||
CHECK(id1 != 0, "post assigns id");
|
||
|
||
/* 媒体通知:ongoing + 进度 */
|
||
memset(&n, 0, sizeof n);
|
||
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
|
||
snprintf(n.category, sizeof n.category, "media");
|
||
snprintf(n.summary, sizeof n.summary, "正在播放");
|
||
n.expire_timeout = 0xFFFFFFFFu; n.ongoing = 1; n.progress = 20; n.urgency = 2;
|
||
uint32_t id2 = 0;
|
||
CHECK(meow_notif_post(&n, &id2) == NOTIF_OK, "post media ok");
|
||
|
||
/* 高紧急 + 进度 */
|
||
memset(&n, 0, sizeof n);
|
||
snprintf(n.app_id, sizeof n.app_id, "system");
|
||
snprintf(n.category, sizeof n.category, "event");
|
||
snprintf(n.summary, sizeof n.summary, "系统提醒");
|
||
snprintf(n.body, sizeof n.body, "电量低");
|
||
n.progress = 50; n.urgency = 3;
|
||
uint32_t id3 = 0;
|
||
CHECK(meow_notif_post(&n, &id3) == NOTIF_OK, "post event(high) ok");
|
||
|
||
CHECK(meow_notif_count(NULL) == 3, "count == 3 after posts");
|
||
|
||
/* 遍历(面板 ncp_collect_cb 同款字段读取)*/
|
||
int got = grab_list();
|
||
CHECK(got == 3, "list returns 3 active notifs");
|
||
|
||
gslot_t *s1 = find_slot("你好世界");
|
||
CHECK(s1 != NULL, "panel sees summary 你好世界");
|
||
if (s1) {
|
||
CHECK(!strcmp(s1->app, "os.meow.hello"), "app_id");
|
||
CHECK(!strcmp(s1->body, "这是正文第二行"), "body");
|
||
CHECK(!strcmp(s1->icon, "apps/os.meow.hello/icon.png"), "app_icon");
|
||
CHECK(!strcmp(s1->tgt, "activity_main"), "target_activity");
|
||
CHECK(s1->progress == -1, "general progress=-1 (none)");
|
||
CHECK(s1->urgency == 1, "general urgency=NORMAL(1)");
|
||
CHECK(s1->ms > 0, "created_ms set");
|
||
}
|
||
gslot_t *s2 = find_slot("正在播放");
|
||
CHECK(s2 != NULL && s2->progress == 20 && s2->urgency == 2, "media progress=20 urgency=HIGH(2)");
|
||
gslot_t *s3 = find_slot("系统提醒");
|
||
CHECK(s3 != NULL && s3->progress == 50 && s3->urgency == 3, "event progress=50 urgency=CRITICAL(3)");
|
||
|
||
/* dismiss → 面板列表减一(点通知=阅读)*/
|
||
CHECK(meow_notif_dismiss(id1) == NOTIF_OK, "dismiss general ok");
|
||
CHECK(meow_notif_count(NULL) == 2, "count == 2 after dismiss");
|
||
grab_list();
|
||
CHECK(find_slot("你好世界") == NULL, "dismissed notif no longer listed");
|
||
|
||
/* 里程碑 100% → 释放(非 ONGOING):面板列表再减一 */
|
||
CHECK(meow_notif_update_progress(id3, 100) == NOTIF_OK, "update_progress(100) ok");
|
||
CHECK(meow_notif_count(NULL) == 1, "milestone released event slot -> count 1");
|
||
grab_list();
|
||
CHECK(find_slot("系统提醒") == NULL, "100% milestone notif no longer listed");
|
||
|
||
/* 剩余媒体通知仍活跃(ONGOING 常驻)*/
|
||
CHECK(find_slot("正在播放") != NULL, "ongoing media stays listed");
|
||
|
||
/* 配额:单 app 媒体单例(media_quota=1)——再发一条 media 顶掉旧媒体 */
|
||
memset(&n, 0, sizeof n);
|
||
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
|
||
snprintf(n.category, sizeof n.category, "media");
|
||
snprintf(n.summary, sizeof n.summary, "下一首");
|
||
n.expire_timeout = 0xFFFFFFFFu; n.ongoing = 1; n.progress = 60;
|
||
uint32_t id4 = 0;
|
||
CHECK(meow_notif_post(&n, &id4) == NOTIF_OK, "post second media ok");
|
||
CHECK(meow_notif_count(NULL) == 1, "media quota=1 -> old media replaced, count stays 1");
|
||
grab_list();
|
||
CHECK(find_slot("下一首") != NULL && find_slot("正在播放") == NULL,
|
||
"media singleton replaced oldest");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
char tmp[] = "/tmp/meow_notif_panel_XXXXXX";
|
||
char *dir = mkdtemp(tmp);
|
||
if (!dir) { perror("mkdtemp"); return 2; }
|
||
snprintf(g_tflog, sizeof g_tflog, "%s/notif.log", dir);
|
||
meow_notif_set_tf_path(g_tflog);
|
||
meow_notif_set_tf_max(1024 * 1024);
|
||
|
||
test_pure_logic();
|
||
test_storage_contract();
|
||
|
||
unlink(g_tflog); rmdir(dir);
|
||
|
||
fprintf(stderr, "\n%s (failures=%d)\n", g_fail ? "== TEST FAILED ==" : "== TEST PASSED ==", g_fail);
|
||
return g_fail ? 1 : 0;
|
||
}
|