真机 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 注入路径。
324 lines
12 KiB
C
324 lines
12 KiB
C
/* ui/lock/lock.c — 锁屏页(ACT_LOCK):APP 启动 PIN(页内自绘数字键盘)。
|
||
*
|
||
* 流程(用户原话「允许用户在APP设置中设置APP启动 PIN」):
|
||
* - lock_view_should_show() = logic/pin 的 mr_pin_enabled();app.c 据此在启动时先 push
|
||
* 本页,验证通过前书库不进入/不绘制(view 注册表只画已进入的页,widget 还按 owner 过滤);
|
||
* - 本页 on_enter 建 12 键数字键盘(1-9 / 退格·0·确认,owner=ACT_LOCK)。**不再调系统
|
||
* 输入法**:真机 goodix 抬手长尾会让 MEOW_IME_NUM 打开即被「点背景=取消」关掉,表现为
|
||
* 「点好几下才弹键盘」;页内键盘完全离线、无 IME 会话,首次点按必响应;
|
||
* - 4-8 位数字(logic/pin 的 MR_PIN_MIN_LEN/MAX_LEN),顶部只显示星号/圆点数位,不回显明文;
|
||
* 满 4 位后「确认」才可用;错误 → 清空 + 提示;通过 → page_pop 回书库;
|
||
* - 长按「退格」清空已输入(不做惩罚性计数/锁定);
|
||
* - 左缘返回**必须**由本页 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c 的
|
||
* on_swipe;否则 app.c 对非 library 页无条件 page_pop,PIN 会被绕过);TAP/LONG_PRESS
|
||
* 也一律消费,物理键不接(返回 0,交 app.c 的 on_key 仅日志);屏底 28px 上滑热区
|
||
* 不被键盘覆盖(键盘底边留 28+12px);
|
||
* - PIN 未启用时 app.c 直接 push 书库,本页不会被进入。
|
||
*
|
||
* 安全口径(诚实标注):PIN 防的是随手翻看,不是安全边界——TF 卡可拔出直读,配置目录
|
||
* 可改。存储为加盐迭代哈希(logic/pin.c),不引入加密库;锁屏文案也如实告知用户。
|
||
*
|
||
* 控件归属(§6.1,M3 桩注释①):on_create 时 activity 栈为空,控件一律在 on_enter 建。
|
||
* 键位顺序(宿主测试依赖):1..9、退格、0、确认 = 下标 0..11。
|
||
*/
|
||
#include "tuku_internal.h"
|
||
#include "logic/pin.h"
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#define LK_KEY_COUNT 12
|
||
#define LK_GRID_COLS 3
|
||
#define LK_GRID_ROWS 4
|
||
#define LK_GAP 10 /* 键间距(≥8;触摸目标互不粘连) */
|
||
#define LK_KEY_MIN_H 64 /* 最小键高 */
|
||
#define LK_KEY_MAX_H 96
|
||
#define LK_KEY_MAX_W 160
|
||
#define LK_SIDE 24 /* 键盘左右留白 */
|
||
#define LK_BOTTOM_HOT 28 /* 屏底上滑回桌面热区:键盘不得覆盖 */
|
||
#define LK_BOTTOM_PAD 12 /* 再留一点呼吸,避免贴住热区 */
|
||
#define LK_HEAD_MIN 220 /* 键盘上方留给标题/圆点/提示的最小高度 */
|
||
|
||
/* 键下标 = 创建序(宿主测试依赖此序)。 */
|
||
enum { LK_K1 = 0, LK_K9 = 8, LK_KBACK = 9, LK_K0 = 10, LK_KOK = 11 };
|
||
|
||
static mr_page_ctrl g_ctrl;
|
||
|
||
static meow_widget *g_keys[LK_KEY_COUNT];
|
||
|
||
static char g_pin[MR_PIN_MAX_LEN + 1];
|
||
static int g_pin_len;
|
||
static char g_msg[64];
|
||
static int g_msg_err;
|
||
|
||
/* meow_view on_event 的事件码:公共伞头未导出(sdk/meow_ui_internal.h:
|
||
* MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3 / MEOW_EV_LONG_PRESS=8,值稳定),按契约镜像。 */
|
||
#define MR_VEV_TAP 2
|
||
#define MR_VEV_SWIPE 3
|
||
#define MR_VEV_LONG_PRESS 8
|
||
|
||
int lock_view_should_show(void)
|
||
{
|
||
return mr_pin_enabled();
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 几何(480×720:键 ~137×96,四行 + 屏底热区留白) */
|
||
/* ============================================================ */
|
||
static void lock_keypad_geom(int *x0, int *y0, int *kw, int *kh)
|
||
{
|
||
int w = meow_ui_width();
|
||
int h = meow_ui_height();
|
||
int sb = meow_ui_statusbar_height();
|
||
int bottom = h - LK_BOTTOM_HOT - LK_BOTTOM_PAD;
|
||
int top_min = sb + LK_HEAD_MIN;
|
||
|
||
int kw_ = (w - 2 * LK_SIDE - (LK_GRID_COLS - 1) * LK_GAP) / LK_GRID_COLS;
|
||
if (kw_ > LK_KEY_MAX_W) kw_ = LK_KEY_MAX_W;
|
||
if (kw_ < LK_KEY_MIN_H) kw_ = LK_KEY_MIN_H;
|
||
|
||
int kh_ = (bottom - top_min - (LK_GRID_ROWS - 1) * LK_GAP) / LK_GRID_ROWS;
|
||
if (kh_ > LK_KEY_MAX_H) kh_ = LK_KEY_MAX_H;
|
||
if (kh_ < LK_KEY_MIN_H) kh_ = LK_KEY_MIN_H;
|
||
|
||
*kw = kw_;
|
||
*kh = kh_;
|
||
*x0 = (w - (LK_GRID_COLS * kw_ + (LK_GRID_COLS - 1) * LK_GAP)) / 2;
|
||
*y0 = bottom - (LK_GRID_ROWS * kh_ + (LK_GRID_ROWS - 1) * LK_GAP);
|
||
if (*y0 < top_min) *y0 = top_min; /* 极小屏兜底;480×720 不会触发 */
|
||
}
|
||
|
||
static void lock_key_rect(int idx, int *x, int *y, int *w, int *h)
|
||
{
|
||
int x0, y0, kw, kh;
|
||
lock_keypad_geom(&x0, &y0, &kw, &kh);
|
||
int row = idx / LK_GRID_COLS, col = idx % LK_GRID_COLS;
|
||
*x = x0 + col * (kw + LK_GAP);
|
||
*y = y0 + row * (kh + LK_GAP);
|
||
*w = kw;
|
||
*h = kh;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* PIN 状态 */
|
||
/* ============================================================ */
|
||
static void lock_refresh(void)
|
||
{
|
||
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
|
||
}
|
||
|
||
/* 「确认」仅在 ≥4 位时可用;其余情况保持禁用(SDK 按状态机变淡且不响应命中)。 */
|
||
static void lock_sync_confirm(void)
|
||
{
|
||
if (g_keys[LK_KOK])
|
||
meow_widget_set_enabled(g_keys[LK_KOK], g_pin_len >= MR_PIN_MIN_LEN);
|
||
}
|
||
|
||
static void lock_clear_digits(void)
|
||
{
|
||
g_pin_len = 0;
|
||
g_pin[0] = '\0';
|
||
lock_sync_confirm();
|
||
}
|
||
|
||
static void lock_append_digit(int d)
|
||
{
|
||
if (g_pin_len >= MR_PIN_MAX_LEN) return; /* 8 位上限:多余按键忽略 */
|
||
g_pin[g_pin_len++] = (char)('0' + d);
|
||
g_pin[g_pin_len] = '\0';
|
||
g_msg[0] = '\0';
|
||
g_msg_err = 0;
|
||
lock_sync_confirm();
|
||
lock_refresh();
|
||
}
|
||
|
||
static void lock_backspace(void)
|
||
{
|
||
if (g_pin_len > 0) g_pin[--g_pin_len] = '\0';
|
||
g_msg[0] = '\0';
|
||
g_msg_err = 0;
|
||
lock_sync_confirm();
|
||
lock_refresh();
|
||
}
|
||
|
||
static void lock_clear_all(void)
|
||
{
|
||
lock_clear_digits();
|
||
g_msg[0] = '\0';
|
||
g_msg_err = 0;
|
||
lock_refresh();
|
||
}
|
||
|
||
static void lock_confirm(void)
|
||
{
|
||
if (g_pin_len < MR_PIN_MIN_LEN) {
|
||
snprintf(g_msg, sizeof g_msg, "请输入至少 %d 位数字", MR_PIN_MIN_LEN);
|
||
g_msg_err = 1;
|
||
lock_refresh();
|
||
return;
|
||
}
|
||
if (!mr_pin_verify(g_pin)) { /* 与设置页同一校验入口(logic/pin) */
|
||
meow_log(MEOW_LOG_INFO, "lock: pin wrong (len=%d), buffer cleared", g_pin_len);
|
||
snprintf(g_msg, sizeof g_msg, "PIN 错误,请重试");
|
||
g_msg_err = 1;
|
||
lock_clear_digits();
|
||
lock_refresh();
|
||
return;
|
||
}
|
||
g_msg[0] = '\0';
|
||
g_msg_err = 0;
|
||
meow_log(MEOW_LOG_INFO, "lock: pin ok");
|
||
meow_view_page_pop(); /* 通过 → 回书库(ACT_LIBRARY on_enter) */
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 布局/绘制 */
|
||
/* ============================================================ */
|
||
static void lock_draw(meow_view *v, const meow_theme *th, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
int w = meow_ui_width();
|
||
int h = meow_ui_height();
|
||
int sb = meow_ui_statusbar_height();
|
||
|
||
int x0, y0, kw, kh;
|
||
lock_keypad_geom(&x0, &y0, &kw, &kh);
|
||
(void)x0; (void)kw; (void)kh;
|
||
|
||
/* 自绘整页底色:验证前不露任何书库内容(本页是顶层唯一 entered 页)。 */
|
||
meow_ui_fill_rect(0, sb, w, h - sb, th->bg);
|
||
|
||
int title_y = sb + 36;
|
||
meow_ui_text_center(w / 2, title_y, "图库已锁定", th->font_title, th->onSurface);
|
||
|
||
/* 已输入位数:星号=已输入,点=空位;不回显明文。 */
|
||
char dots[MR_PIN_MAX_LEN * 2 + 1];
|
||
int n = 0;
|
||
for (int i = 0; i < MR_PIN_MAX_LEN; i++) {
|
||
dots[n++] = (i < g_pin_len) ? '*' : '.';
|
||
if (i + 1 < MR_PIN_MAX_LEN) dots[n++] = ' ';
|
||
}
|
||
dots[n] = '\0';
|
||
int dots_y = title_y + meow_ui_text_height(th->font_title) + 18;
|
||
meow_ui_text_center(w / 2, dots_y, dots, th->font_title,
|
||
g_pin_len > 0 ? th->primary : th->onSurfaceVariant);
|
||
|
||
int status_y = dots_y + meow_ui_text_height(th->font_title) + 8;
|
||
if (g_msg[0])
|
||
meow_ui_text_center(w / 2, status_y, g_msg, th->font_body,
|
||
g_msg_err ? th->error : th->primary);
|
||
else
|
||
meow_ui_text_center(w / 2, status_y, "输入 4-8 位数字后点确认", th->font_body,
|
||
th->onSurfaceVariant);
|
||
|
||
/* 安全口径(如实告知),紧贴键盘上方。 */
|
||
int note_y = y0 - 54;
|
||
meow_ui_text_center(w / 2, note_y, "PIN 仅用于防止随手查看,", th->font_body,
|
||
th->onSurfaceVariant);
|
||
meow_ui_text_center(w / 2, note_y + 24, "TF 卡可被直接读取,不构成安全边界。", th->font_body,
|
||
th->onSurfaceVariant);
|
||
}
|
||
|
||
static void lock_place(meow_view *v)
|
||
{
|
||
int w = meow_ui_width();
|
||
int h = meow_ui_height();
|
||
v->x = 0; v->y = 0; v->w = w; v->h = h;
|
||
|
||
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||
int x, y, kw, kh;
|
||
lock_key_rect(i, &x, &y, &kw, &kh);
|
||
if (g_keys[i]) meow_widget_set_rect(g_keys[i], x, y, kw, kh);
|
||
}
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 事件 */
|
||
/* ============================================================ */
|
||
static int lock_on_event(meow_view *v, int ev, int a, int b, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
|
||
/* 左缘返回(以及任何滑动)一律吞掉:锁屏不允许退出 App,也不允许绕过 PIN。 */
|
||
if (ev == MR_VEV_SWIPE) return 1;
|
||
|
||
if (ev == MR_VEV_LONG_PRESS) {
|
||
/* 长按「退格」= 清空(可选增强;其余长按也只吞掉,不透传其它页)。 */
|
||
if (meow_widget_hit(g_keys[LK_KBACK], a, b)) lock_clear_all();
|
||
return 1;
|
||
}
|
||
|
||
if (ev == MR_VEV_TAP) {
|
||
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||
if (!g_keys[i] || !meow_widget_hit(g_keys[i], a, b)) continue;
|
||
if (i <= LK_K9) lock_append_digit(i + 1); /* 1..9 */
|
||
else if (i == LK_KBACK) lock_backspace();
|
||
else if (i == LK_K0) lock_append_digit(0);
|
||
else lock_confirm();
|
||
return 1;
|
||
}
|
||
return 1; /* 锁屏页消费所有点击(含空白区),不向 App 其它页透传 */
|
||
}
|
||
return 0; /* 物理键交 app.c 的 on_key 仅日志(不映射数字/确认) */
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* view 生命周期 */
|
||
/* ============================================================ */
|
||
static void lock_page_enter(meow_view *v, void *ctx)
|
||
{
|
||
((mr_page_ctrl *)ctx)->active = 1;
|
||
meow_ui_set_fullscreen(0);
|
||
meow_ui_set_page_bg(meow_theme_get()->bg);
|
||
|
||
g_pin_len = 0;
|
||
g_pin[0] = '\0';
|
||
g_msg[0] = '\0';
|
||
g_msg_err = 0;
|
||
|
||
static const char *const labels[LK_KEY_COUNT] = {
|
||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "退格", "0", "确认",
|
||
};
|
||
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||
if (g_keys[i]) continue;
|
||
/* page_push 已先 meow_ui_activity_push(ACT_LOCK) → 控件 owner 正确。 */
|
||
int variant = (i == LK_KOK) ? MEOW_BTN_FILLED
|
||
: (i == LK_KBACK) ? MEOW_BTN_OUTLINED
|
||
: MEOW_BTN_TONAL;
|
||
g_keys[i] = meow_btn_create_variant(0, 0, 96, LK_KEY_MIN_H, labels[i], variant);
|
||
if (g_keys[i]) meow_view_add_widget(v, g_keys[i]);
|
||
}
|
||
|
||
lock_place(v);
|
||
lock_sync_confirm();
|
||
meow_log(MEOW_LOG_INFO, "lock: pad ready (12 keys, screen %dx%d)",
|
||
meow_ui_width(), meow_ui_height());
|
||
mr_nav_report();
|
||
meow_ui_paint_full();
|
||
}
|
||
|
||
static void lock_page_leave(meow_view *v, void *ctx)
|
||
{
|
||
(void)v;
|
||
((mr_page_ctrl *)ctx)->active = 0;
|
||
}
|
||
|
||
static void lock_page_destroy(meow_view *v, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
for (int i = 0; i < LK_KEY_COUNT; i++) g_keys[i] = NULL;
|
||
/* 控件随 meow_view_free 释放(v->widgets) */
|
||
}
|
||
|
||
meow_view *lock_view_create(void)
|
||
{
|
||
static const meow_view_hooks h = {
|
||
.on_enter = lock_page_enter,
|
||
.on_leave = lock_page_leave,
|
||
.on_destroy = lock_page_destroy,
|
||
.on_draw = lock_draw,
|
||
.on_event = lock_on_event,
|
||
};
|
||
return meow_view_create(ACT_LOCK, MEOW_VIEW_ACTIVITY, &h, &g_ctrl);
|
||
}
|