Files
MeowOS-SDK/tuku/test/test_settings_lock_host.c
T
AskaEth 606a04a44e 图库: 锁屏 PIN 改页内自绘 12 键数字键盘(不再弹系统 IME)
真机 goodix 抬手长尾会让阻塞式 MEOW_IME_NUM 打开即被「点背景=取消」
关掉,表现为「PIN 按钮点好几下才弹键盘」。锁屏改自绘 3×4 键盘
(1-9 / 退格·0·确认,键 137×96、间距 10px、底边留 28px 上滑热区),
完全离线、无 IME 会话,首次点按必响应。

- 4-8 位(MR_PIN_MIN_LEN/MAX_LEN),顶部星号显示已输入位数,不回显明文;
  满 4 位后「确认」才可用;错误→提示+清空;通过→page_pop 回书库
- 长按退格清空;退格空缓冲为 no-op;8 位上限截断
- 颜色全部取 meow_theme_get();按键走 meow_btn_create_variant(TONAL/
  OUTLINED/FILLED),沿用 SDK 按压高亮/禁用变淡
- 锁屏仍消费全部 SWIPE/TAP/LONG_PRESS(左缘绕过 PIN 的路径不变),
  物理键返回 0 交 app.c on_key 仅日志
- 与设置页共用 logic/pin 的 mr_pin_verify/mr_pin_set,不复制校验逻辑
- 日志:lock: pad ready / pin wrong / pin ok(真机验收判据)

test_settings_lock_host.c:锁屏用例从 IME 注入改为按键点击(12 键归属、
键高/间距/热区几何、空确认/不足 4 位、错 PIN 清空重输、8 位上限+退格、
空退格、长按清空、不打开系统 IME);设置页仍走真实 IME 注入路径。
2026-11-06 11:46:31 +08:00

327 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_settings_lock_host.c — 设置页/锁屏与 SDK 骨架的宿主集成测试(可选,需 SDK 源码树)。
*
* 目的:钉住 M3 骨架评审点名的两条坑(M5 桩注释①/②),这两条只有把真实 SDK
* view/activity/widget 链起来才能验证:
* ① 非根页控件必须在 on_enter 建(此时 activity 栈顶=本页)——否则 owner 绑到
* ACT_LIBRARY,跨页命中泄漏;
* ② 锁屏必须在自身 view 的 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c
* 的 on_swipe),否则左缘返回会把 ACT_LOCK page_pop 掉、PIN 被绕过。
* 另外覆盖:设置页回到书库(topbar 返回)、控件跨进出复用不重建、阅读方向经
* mr_reader_set_direction 切换、lock_view_should_show() 随 PIN 状态变化、
* 主题模式切换后整帧重绘不崩,以及两条端到端路径:
* - **锁屏页内自绘 12 键数字键盘**(1-9 / 退格·0·确认):错 PIN 留在锁屏 → 对 PIN
* page_pop 回书库;位数不足确认不可用、超长输入截断、退格/长按清空、错误后清空重输;
* 键高/间距/屏底 28px 热区几何契约;
* - 设置页两遍输入开启 PIN(仍走真实 MEOW_IME_NUM 键盘注入)。
*
* 编译:见 test/run_tests.sh —— 它把本测试、settings/lock/pin 源码与 SDK 源码
* (-DMEOW_UI_HOST_TEST,附 test_host_stub.c 打桩 font/global)一起编;SDK 对象清单在脚本里。
* 只发布 meow_ui.a(无 SDK 源码)的交付环境,run_tests.sh 自动 SKIP 本测试。
*/
#include "tuku_internal.h"
#include "logic/pin.h"
#include "meow_ui_internal.h" /* meow_widget_owner(宿主测试专用内部头) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
static int g_fail;
#define CHECK(cond, ...) do { \
if (cond) { printf(" PASS\n"); } \
else { g_fail++; printf(" FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
} while (0)
/* app.c 的导航上报在测试里不需要网络侧实现。 */
void mr_nav_report(void) {}
static int g_lib_enters;
static void lib_enter(meow_view *v, void *c) { (void)v; (void)c; g_lib_enters++; }
static void lib_leave(meow_view *v, void *c) { (void)v; (void)c; }
/* 清掉本次测试写入的三个小配置并删临时目录(meow_config 每键一文件)。 */
static void cleanup_cfg(const char *dir)
{
static const char *keys[3] = { "pin.enabled.cfg", "pin.hash.cfg", "pin.salt.cfg" };
char p[512];
for (int i = 0; i < 3; i++) {
snprintf(p, sizeof p, "%s/%s", dir, keys[i]);
unlink(p);
}
rmdir(dir);
}
/* ============================================================ */
/* 数字键盘注入(宿主没有真实触摸):按 MEOW_IME_NUM 键位投递 TAP,关闭键盘后由被测代码校验。
* NUM 布局(sdk/meow_ui_ime.c build_kb):KB_TOP=420;首行 470 是符号行 "-/.:*#+=",
* 数字九键行为 518/566/614(x0=70/190/310,键宽 100),底行(ESC|0|退格+OK)为 662;
* 点 y<420 的背景=关闭键盘。 */
/* ============================================================ */
typedef struct { const char *digits; int passes; } pin_job;
static void key_tap(char d)
{
int row, col;
if (d == '0') { row = 3; col = 1; }
else {
int v = d - '1';
if (v < 0 || v > 8) return;
row = v / 3;
col = v % 3;
}
/* 数字行 y=518/566/614(首行 470 是符号行 "-/.:*#+="),底行 662;列心 120/240/360。 */
meow_input_inject(MEOW_EV_TAP, 70 + col * 120 + 50, 518 + row * 48 + 18);
}
static void *pin_injector(void *arg)
{
pin_job *job = (pin_job *)arg;
usleep(150000); /* 等 text_input 开键盘并 drain 完残留事件 */
for (int p = 0; p < job->passes; p++) {
if (p) usleep(300000); /* 两遍输入之间等第二遍开键盘 */
for (const char *s = job->digits; *s; s++) {
key_tap(*s);
usleep(50000);
}
usleep(50000);
meow_input_inject(MEOW_EV_TAP, 240, 100); /* y<420:关闭键盘(非空→校验) */
}
return NULL;
}
/* ============================================================ */
/* 锁屏页内自绘 12 键数字键盘(宿主没有真实触摸):按 widget 矩形中心投递 TAP。
* 键下标 = lock.c 创建序:0..8 = 1..9,9 = 退格,10 = 0,11 = 确认(LK_K*)。
* ============================================================ */
enum { LK_K1 = 0, LK_K9 = 8, LK_KBACK = 9, LK_K0 = 10, LK_KOK = 11 };
static int lock_key_center(meow_view *lock, int idx, int *cx, int *cy)
{
int r[4] = { 0 };
if (!lock || idx < 0 || idx >= lock->nwidgets || !lock->widgets[idx]) return 0;
meow_widget_rect(lock->widgets[idx], r);
*cx = r[0] + r[2] / 2;
*cy = r[1] + r[3] / 2;
return 1;
}
static void lock_tap_key(meow_view *lock, int idx)
{
int cx, cy;
if (lock_key_center(lock, idx, &cx, &cy))
meow_view_dispatch_event(MEOW_EV_TAP, cx, cy);
}
static int lock_key_idx(char d)
{
return (d == '0') ? LK_K0 : (d - '1');
}
static void lock_type(meow_view *lock, const char *digits)
{
for (const char *s = digits; *s; s++) lock_tap_key(lock, lock_key_idx(*s));
}
static void lock_long_back(meow_view *lock)
{
int cx, cy;
if (lock_key_center(lock, LK_KBACK, &cx, &cy))
meow_view_dispatch_event(MEOW_EV_LONG_PRESS, cx, cy);
}
static void settings_tap_pin_row(void){
/* 布局:sb + 56 顶栏 + 6 + 段头34 + 方向行64 + 12 + 段头34 → PIN 列表首行中心 +32 */
int y = meow_ui_statusbar_height() + 56 + 6 + 34 + 64 + 12 + 34 + 32;
pin_job job = { "2468", 2 };
pthread_t th;
if (pthread_create(&th, NULL, pin_injector, &job) != 0) return;
meow_view_dispatch_event(MEOW_EV_TAP, 200, y);
pthread_join(th, NULL);
}
int main(void)
{
const char *base = getenv("TMPDIR");
if (!base || !base[0]) base = ".";
char tmpl[512];
snprintf(tmpl, sizeof tmpl, "%s/mr_settings_host_XXXXXX", base);
char *data = mkdtemp(tmpl);
if (!data) { printf("SKIP: mkdtemp failed\n"); return 0; }
setenv("MEOW_DATA_DIR", data, 1);
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { printf("SKIP: meow_ui_init failed (no host backend)\n"); return 0; }
static const meow_view_hooks lh = { .on_enter = lib_enter, .on_leave = lib_leave };
meow_view *lib = meow_view_create(ACT_LIBRARY, MEOW_VIEW_ACTIVITY, &lh, NULL);
meow_view *settings = settings_view_create();
meow_view *lock = lock_view_create();
int sb = meow_ui_statusbar_height();
meow_view_page_push(ACT_LIBRARY);
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "start at library");
/* ---- 设置页:on_enter 建控件 + owner 归属 ---- */
meow_view_page_push(ACT_SETTINGS);
CHECK(meow_ui_activity_active() == ACT_SETTINGS, "settings active");
CHECK(settings->nwidgets == 5, "settings built 5 widgets (got %d)", settings->nwidgets);
int owner_ok = 1;
for (int i = 0; i < settings->nwidgets; i++)
if (meow_widget_owner(settings->widgets[i]) != ACT_SETTINGS) owner_ok = 0;
CHECK(owner_ok, "settings widgets owned by ACT_SETTINGS (no cross-page leak)");
/* 布局几何:顶栏贴状态栏、三组列表不越界、页脚在屏内。 */
int r_tb[4] = { 0 }, r_about[4] = { 0 };
meow_widget_rect(settings->widgets[0], r_tb);
meow_widget_rect(settings->widgets[3], r_about);
CHECK(r_tb[1] == sb && r_tb[3] == 56, "topappbar sits below statusbar");
CHECK(r_about[1] + r_about[3] < meow_ui_height() - 60,
"about list leaves room for footer (bottom=%d)", r_about[1] + r_about[3]);
printf(" geometry: topbar y=%d, about list y=%d h=%d (screen %dx%d)\n",
r_tb[1], r_about[1], r_about[3], meow_ui_width(), meow_ui_height());
/* 阅读方向行:段头 34 + 行中心。 */
int dir_y = sb + 56 + 6 + 34 + 32;
int d0 = mr_reader_get_direction();
CHECK(meow_view_dispatch_event(MEOW_EV_TAP, 120, dir_y) == 1,
"tap on direction row consumed by settings on_event");
CHECK(mr_reader_get_direction() != d0,
"direction toggled through mr_reader_set_direction");
/* topbar 返回 → page_pop 回书库 */
CHECK(meow_view_dispatch_event(MEOW_EV_TAP, 22, sb + 28) == 1, "back tap consumed");
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "back popped to library");
CHECK(g_lib_enters == 2, "library re-entered after pop (enters=%d)", g_lib_enters);
/* 再次进入:控件复用(不在 on_enter 重复创建/泄漏) */
meow_view_page_push(ACT_SETTINGS);
CHECK(settings->nwidgets == 5, "settings widgets reused (got %d)", settings->nwidgets);
CHECK(meow_ui_activity_active() == ACT_SETTINGS, "settings active again");
/* 设置页不消费 SWIPE:交给 app.c 的 page_pop */
CHECK(meow_view_dispatch_event(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 200) == 0,
"settings leaves SWIPE to app.c (back = pop)");
meow_view_page_pop(); /* 手动模拟 app.c 的返回行为,回到书库 */
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "manual pop back to library");
/* ---- 锁屏:PIN 状态驱动 + SWIPE 拦截(坑②) ---- */
mr_pin_disable();
CHECK(lock_view_should_show() == 0, "lock hidden when PIN disabled");
CHECK(mr_pin_set("1357") == MR_PIN_OK, "enable PIN");
CHECK(lock_view_should_show() == 1, "lock shown when PIN enabled");
meow_view_page_push(ACT_LOCK);
CHECK(meow_ui_activity_active() == ACT_LOCK, "lock active");
CHECK(lock->nwidgets == 12, "lock built 12-key pad on enter (got %d)", lock->nwidgets);
int lock_owner_ok = 1;
for (int i = 0; i < lock->nwidgets; i++)
if (meow_widget_owner(lock->widgets[i]) != ACT_LOCK) lock_owner_ok = 0;
CHECK(lock_owner_ok, "pad keys owned by ACT_LOCK (no cross-page leak)");
/* 几何契约:键 ≥64px 高、间距 ≥8px、不覆盖屏底 28px 上滑热区、全在屏内。 */
int rk[12][4];
for (int i = 0; i < 12; i++) meow_widget_rect(lock->widgets[i], rk[i]);
int geo_ok = 1, gap_ok = 1, hot_ok = 1;
for (int i = 0; i < 12; i++) {
if (rk[i][2] < 64 || rk[i][3] < 64) geo_ok = 0;
if (rk[i][0] < 0 || rk[i][0] + rk[i][2] > meow_ui_width()) geo_ok = 0;
if (rk[i][1] + rk[i][3] > meow_ui_height() - 28) hot_ok = 0;
}
/* 列间距(行内 0/1/2)与行间距(行 0 与行 3:下标 0 与 9)。 */
if (rk[1][0] - (rk[0][0] + rk[0][2]) < 8) gap_ok = 0;
if (rk[2][0] - (rk[1][0] + rk[1][2]) < 8) gap_ok = 0;
if (rk[9][1] - (rk[0][1] + rk[0][3]) < 8) gap_ok = 0;
CHECK(geo_ok, "pad keys >=64px and within screen");
CHECK(gap_ok, "pad gaps >=8px");
CHECK(hot_ok, "pad leaves the 28px bottom swipe hot zone");
printf(" pad geometry: key %dx%d, pad top=%d bottom=%d (screen %dx%d)\n",
rk[0][2], rk[0][3], rk[0][1], rk[11][1] + rk[11][3],
meow_ui_width(), meow_ui_height());
CHECK(meow_view_dispatch_event(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 240) == 1,
"lock consumes SWIPE (left-edge back cannot bypass PIN)");
CHECK(meow_ui_activity_active() == ACT_LOCK, "still locked after swipe");
CHECK(meow_view_dispatch_event(MEOW_EV_TAP, 5, 400) == 1, "lock consumes stray taps");
CHECK(meow_ui_activity_active() == ACT_LOCK, "still locked after stray tap");
CHECK(mr_pin_verify("1357") == 1, "stored PIN verifies");
CHECK(mr_pin_verify("1358") == 0, "wrong PIN rejected");
/* 空输入 / 位数不足:确认不可用(不误判、不退出锁屏)。 */
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LOCK, "empty confirm keeps lock");
lock_type(lock, "135");
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LOCK, "confirm disabled below 4 digits");
/* 第 4 位后确认可用:1-3-5-7 → 回书库。 */
lock_tap_key(lock, lock_key_idx('7'));
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "4-digit correct PIN pops lock to library");
CHECK(meow_ime_active() == 0, "in-page pad never opens the system IME");
/* 错 PIN:留在锁屏 + 清空;再输对 → 解锁(证明错误后缓冲已清空)。 */
meow_view_page_push(ACT_LOCK);
lock_type(lock, "1358");
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LOCK, "wrong PIN keeps lock (retry allowed)");
lock_type(lock, "1357");
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LIBRARY,
"retry after wrong PIN unlocks (buffer cleared on error)");
/* 8 位上限:9 个键只收前 8 位;退格 4 次回到 "1357" → 解锁。 */
meow_view_page_push(ACT_LOCK);
lock_type(lock, "135713579");
for (int i = 0; i < 4; i++) lock_tap_key(lock, LK_KBACK);
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LIBRARY,
"9th digit ignored (max 8) + backspace removes digits");
/* 退格空缓冲边界:多按不崩,随后正常输入。 */
meow_view_page_push(ACT_LOCK);
for (int i = 0; i < 3; i++) lock_tap_key(lock, LK_KBACK);
lock_type(lock, "1357");
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "backspace on empty buffer is a no-op");
/* 长按退格清空:先输 3 位 → 长按清空 → 再输 4 位正确 PIN 才解锁。 */
meow_view_page_push(ACT_LOCK);
lock_type(lock, "135");
lock_long_back(lock);
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LOCK, "long-press backspace cleared input");
lock_type(lock, "1357");
lock_tap_key(lock, LK_KOK);
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "unlock after long-press clear");
/* ---- 设置页两遍输入开启 PIN(MEOW_IME_NUM) ---- */
mr_pin_disable();
meow_view_page_push(ACT_SETTINGS);
CHECK(meow_ui_activity_active() == ACT_SETTINGS, "settings active for PIN enable");
settings_tap_pin_row();
CHECK(mr_pin_enabled() == 1, "two-pass entry enabled the PIN");
CHECK(mr_pin_verify("2468") == 1, "PIN entered in settings verifies");
meow_view_page_pop();
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "back from settings to library");
/* 主题切换:app.c listener 整帧 invalidate;页面按 meow_theme_get() 重画不崩。 */
meow_theme_set_mode(MEOW_THEME_LIGHT);
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
meow_theme_set_mode(MEOW_THEME_DARK);
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
CHECK(1, "theme switch repaint does not crash");
mr_pin_disable();
meow_view_free(lock);
meow_view_free(settings);
meow_view_free(lib);
meow_ui_shutdown();
cleanup_cfg(data);
printf("%s: %s\n", g_fail ? "FAIL" : "PASS",
g_fail ? "see failures above" : "integration checks passed");
return g_fail ? 1 : 0;
}