Imported from zuige66/Hyper-MeloLock (
AGENTS.md). Install upstream withnpx skills add zuige66/Hyper-MeloLock. Copyright stays with the author.
AGENTS.md
项目约定
- 完成代码改动后同步更新相关 Markdown 文档。
README.md只做面向使用者的项目介绍(简介、功能、截图、适配、安装、构建、许可),实现细节 / 排查手册 / 变更日志写进docs/DEVELOPMENT.md,工程约定写进本文件。改到截图或图标时同步docs/images/。 - 本项目是 Java Android + Vector/LSPosed 模块,配置端已迁入 HyperIsland 的 Kotlin/Compose/Miuix
app源码;修改配置字段时必须同时检查Config.java、ConfigProvider.java、Compose 页面和 SystemUI 侧读取逻辑。 - 锁屏覆盖层默认失败关闭:找不到目标 SystemUI 视图或媒体数据无效时恢复原生界面。
- 界面只做「复用 HyperIsland 原版组件 + 换数据源」,不新写样式;同名卡片直接提升
OverviewPage.kt里的实现为internal共享,禁止复制第二份。 LockScreenOverlay.java由 Hook 注入 SystemUI 进程:任何改动都必须失败关闭(异常退回原生锁屏),并且不要与其他会话/人工编辑并行改这个文件。- 新增锁屏可调参数时:键名与默认值加到
Config.java的ELEMENT_DEFAULTS,Provider 走/elements的 key/value 通道,不要再去改ConfigProvider的列投影。 - 装完 APK 必须重启 SystemUI 才会加载新的 Hook 代码,顺序是先装再重启。无 root 重启法:
adb -s 1b3a7d8 shell am crash com.android.systemui(实测有效,SystemUI 崩掉后自动重启、PID 立刻变化;am force-stop无效、su从 adb 不可用)。锁屏行为异常时先比ps里 SystemUI 的 ETIME 和 APK 安装时间,再看logcat | grep "Elements: clock="有没有出现——没有就说明跑的还是旧代码。
仓库与发布
- 远端:
git@github.com:zuige66/Hyper-MeloLock.git(分支main,SSH 免密已通)。本机没有ghCLI、也没有 GitHub token,创建 Release / 上传附件必须走 GitHub 网页或 API。 - 签名:
melolock-release.keystore(PKCS12)+keystore.properties都在仓库根,已被.gitignore排除,绝不能提交;丢了就无法给同一个应用升级,务必另存备份。app/build.gradle.kts只在keystore.properties存在时才建releasesigningConfig,否则 release 回退到 debug 签名(那种包不能发 Release)。 - 构建发布包:
./gradlew.bat :app:assembleRelease→app/build/outputs/apk/release/app-release.apk;校验apksigner verify --print-certs -v <apk>。Release 不开混淆(Xposed 模块靠类名/方法名字符串定位),并且关闭了lint.checkReleaseBuilds(迁入的 HyperIsland 多语言资源有第三方库遗留的ExtraTranslation)。 - 版本号:
app/build.gradle.kts的versionCode/versionName,发版时改这里并打同名 tag。 - 不要提交:
local.properties、.workbuddy/、临时调试截图hmsc-*.png、任何*.keystore/*.jks。要提交的展示图放在docs/images/(icon.png为应用图标,供 README 引用)。
最近完成
- README 改版为项目介绍页(2026-10-07):原 README(423 行 / 59KB)整体搬成
docs/DEVELOPMENT.md,内容一行未删,只在顶部加了交接说明并把图标路径改到新位置。新README.md参照 HyperIsland(1812z/HyperIsland)的风格重写:居中图标 + 徽章行 + 两列 emoji 特性表 + 效果预览截图 + 适配表 + 安装 + 构建 + 已知限制 + Star History + 许可。新建docs/images/,放icon.png(用git mv从根目录hyper-melolock.png移来,与应用图标res/drawable-nodpi/ic_hmsc.png同 MD5)和 4 张真机截图lockscreen-player.jpg/lockscreen-notifications.jpg/app-home.jpg/app-appearance.jpg。根目录遗留的两张开发期截图hmsc-lockscreen-test.png(早期原型)与hmsc-v016.png(v0.1.6 通知页)未被任何文档引用、样式已过时,留在原地未处理且仍被.gitignore排除。附:本机访问github.com网页不通(curl 返回 000),但 API 正常,取别人的 README 原文用curl -H "Accept: application/vnd.github.raw" https://api.github.com/repos/<owner>/<repo>/readme。 - 首个 Release(2026-10-07):仓库推送到 GitHub(SSH
git@github.com:zuige66/Hyper-MeloLock.git),补.gitignore排除签名密钥 /.workbuddy// 临时截图;用新建的melolock-release.keystore(PKCS12,v1+v2+v3 全签,SHA-12B:73:26:5B:F5:0D:BA:65:76:A7:75:8C:55:23:40:CD:C5:4E:C1:F7)构建 v0.2.0 release APK。Gradle 坑:Kotlin DSL 里java.util.Properties会解析失败(Unresolved reference 'util'),必须在文件顶部import java.io.FileInputStream/import java.util.Properties;release 还会被 lint 的ExtraTranslation(values-ar 里的androidx_startup,来自迁入资源)拦下,需lint { checkReleaseBuilds = false }。 - 三个真机反馈问题(同一轮):① 展开通知后左下拉仍能拉出通知、原生锁屏时钟与我们的时钟重叠——
sceneShowing()里带了playerSceneVisible,而展开通知时它被置 false → 拦截整段失效(日志:展开期间一条Left-shade gesture consumed都没有)。判据改为「场景存在 ∧ 未 suspend ∧ 锁屏周期 ∧ 前台可见」,不看当前是哪一页。② 桌面左下拉露出「原屏保的时间」——suspended(场景保留但 GONE)期间系统在桌面下拉 shade 时会把原生锁屏时钟重新显示,故在 suspended 分支继续hideNativeClockLayers(root),绝不碰壁纸层(解锁动画要靠它)。③ 切回播放器时间闪一下——真凶是showMusic()在animateIn分支把整个foreground(含时钟)从alpha 0淡入 180ms,而时钟在通知页一直显示着,等于先消失再回来。返回时不再淡入前景;同时通知不再做淡出(淡出层盖在时钟上,任何合成抖动都表现为时间闪烁)。另外把bringToFront()换成只在确实不在最上层时才动的ensureOnTop()(每帧requestLayout是掉帧源),并把共享位移上限从 220dp 收到 96dp + 展开时测量一次缓存复用(真机两个方向量到 -605px / -228px,卡片会穿过时钟区域再从别处回来)。 - 播放器页 ↔ 通知页切换动效(真机反馈"通知先出来、播放器还没消失"):根因是
showNotifications()里show(notifications)让通知瞬间满不透明出现,而播放器还在跑 220ms 淡出。改为共享元素位移过渡——sharedOffsetY()取通知列表第一张大卡片与自绘播放器卡片的屏幕坐标差(夹 ±220dp)当作目的地;展开时播放器沿位移滑走 +alpha→0+scale→0.94,通知延迟 70ms 反向淡入(同向运动);返回时反向重演。两个坑:① pre-draw 守卫每帧按expanded硬设可见性,返回时若不设swappingPages闸门会把淡出瞬间掐断,兜底必须用 Handler(不能挂ViewPropertyAnimator回调,解锁期间可能不推进);②restore()与finishPageSwap都要复位通知栈的alpha/translationY,否则下次show()出来是全透明的。日志Page swap: … shared offset=<n>px,offset 一直是回退值就说明测量没生效。跟进一次卡顿修复("回到播放器时时间卡一下"):① 收起的cover/playerCard由GONE改INVISIBLE——GONE会把视角/卡片从布局摘掉,content这棵竖直 LinearLayout 要重 measure/layout 全树(大图 ImageView + ProgressBar 卡片),切回来再 VISIBLE 又来一次,两次全树 layout 正砸在动画首尾帧;② 切换动画期间给通知栈开硬件层(beginNotificationsLayer()),避免整棵通知子树每帧重绘把主线程拖住;该层必须只在动画期间存在,finishPageSwap/restore()一律LAYER_TYPE_NONE拆掉。 - 切歌掉回原生锁屏(真机日志定位,整改集中在
MediaSource):换歌时 App 会先摘掉METADATA_KEY_ALBUM_ART的 bitmap、只留 URI(SystemUI 常常读不到:content://权限或 App 私有文件),约 300ms 后才补齐;这期间会话也可能短暂不合格。三条旧处理把这个过渡态当成「播放停了」——① URI 解码失败就lastReady = null并下发 null;② 会话一不合格立刻下发 null(→restore("render-no-session"));③ 不合格时把current置空注销回调、错过新封面。现改为:解码失败/超时都保留上一帧;失去会话给 1500ms 宽限期(SESSION_GRACE_MS)继续交出上一帧;空窗期按包名继续跟踪会话保住回调。另外loadArt()失败会打印scheme://authority与异常。日志:Session unavailable … keeping last frame up to 1500ms/Empty gap detected/Media ready from bitmap title=<新歌>。验收:切歌全程不应出现restore reason=render-no-session与create()。注意 blank final 字段(listener)不能在字段初始化器的 lambda 体里引用,会报「可能尚未初始化变量」,用方法引用(this::announceLost)。 - 左侧通知栏下拉 → 改成直接禁用该手势(已装机能挡左下拉):在通知面板(
leftShadePanel,idnotification_panel)的dispatchTouchEvent/onInterceptTouchEvent/onTouchEvent上挂 Xposed 钩子,沉浸场景显示期间对左半屏的 ACTION_DOWN 返回 false → 通知栏不展开;右侧控制中心在独立容器control_center_container,不受影响。判断「场景在显示」用模块自身状态(sceneShowing()),不再依赖任何系统视图的可见性(通知栈可见性、notification_panel可见性两个信号都被真机证伪,见 README)。开关Config.BLOCK_LEFT_SHADE(默认开,外观 → 播放器「锁屏禁止左下拉」),每次手势现读配置。代价:左半屏起始的上滑解锁也会失效。日志Left-shade touch block armed/Left-shade gesture consumed。- 关键坑(第一版真机事故):
NotificationPanelView没覆写dispatchTouchEvent,getMethod拿到的是框架View的实现,钩它等于给 SystemUI 所有 View 装钩。第一版缺hook.thisObject != leftShadePanel判定 → 左半屏触摸被全吃,播放器 ◀/播放暂停 与「展开通知」按钮全部失灵。修复后钩子只在thisObject == leftShadePanel时生效;findTouchMethod()优先取类自己声明的方法,但退回继承实现是常态,这个判定不能删。 - 同一轮删除了 pre-draw 里的逐帧诊断(
Shade watch:/Expansion getters:/Window tree:,每帧读 12 个视图 + 反射 7 个 getter),信号既不可靠又耗 CPU。
- 关键坑(第一版真机事故):
- 解锁残留治本(用户截图定位):前景层与通知按钮从窗口根改挂锁屏根。截图显示解锁瞬间「壁纸已出、前景组件完整残留」——背景层挂锁屏根被系统动画带走,前景挂窗口根不跟动画。挂同一容器后系统退场动画把整层一起带走,消失同步。同时实现「播放即预建」:
render()在桌面收到有效媒体快照且无场景时preCreate()(GONE + suspended),一点播放场景就绪。restore()的 removeView 判据同步改为锁屏根。 - 暂停误判 + 解锁残留(真机日志定位):① 暂停被当成「无会话」把场景销毁——暂停识别原先依赖
lastReady,切歌失败会清空它,之后refresh()就走onMedia(null)→render-no-session撤层,锁屏掉回原生。改为遍历时记住第一个「允许的包 + 有 metadata + 非播放」的会话。② 解锁后前景残留桌面——suspend()把隐藏挂在ViewPropertyAnimator.withEndAction上,解锁时窗口切换、动画回调可能不推进。改为 Handler 延时兜底(finishSuspend),动画只负责视觉淡出。排查手法:logcat -d -v time -s MeloLock | grep -v "Media ready from bitmap"能滤掉每 2 秒的取图噪音,直接看到状态机流转。 - 解锁/亮屏闪屏补完(暂停保留 + 熄屏预建):① 暂停不销毁——
MediaSource区分「会话还在但暂停」和「会话消失」,暂停时交出最后一帧(Snapshot.playing=false、speed=0),并且current继续跟踪该会话以免注销回调后点播放不更新;中间键改成播放/暂停切换。② 熄屏预建——「桌面播歌 → 熄屏 → 亮屏」这条路径上场景从未存在(桌面上render()一律skip("keyguard-unlocked")),suspend/resume救不了,现在ACTION_SCREEN_OFF主动触发一次媒体刷新,render()在!isInteractive()时preCreate():趁着屏幕黑把场景建好并置 GONE +suspended,亮屏由 pre-draw 第一帧resume()秒显。render()抽出applySnapshot()共用。 - 修复上滑解锁闪屏(淡出 + 保留实例):真机抓到的 5 轮「熄屏 → 亮屏 → 解锁」日志证明两个现象同源——解锁瞬间
keyguardGuard见isKeyguardLocked()翻 false 就restore(),而restore()会把原生壁纸层恢复 VISIBLE、系统解锁动画却还要跑 80190ms(145ms,于是「先见原生锁屏」。改为USER_PRESENT才到),于是露壁纸;场景被销毁后下次亮屏必须create()重建 126suspend():淡出 180ms 后保留视图实例并置GONE,suspended期间 pre-draw 守卫与媒体回调都不接管界面;锁屏重现时resume()直接复用同一批视图。只有模块关闭 / 媒体不可用 / 锁屏根视图分离才真正restore()。已构建、已安装,待真机验收。 - ⚠️ 仓库存在并行编辑:2026-10-06 20:00 前后源码从
io/github/hypermusicscape/lock/(TAGHyperMusicScapeLock)整体迁移到io/github/melolock/(TAGMeloLock),且是在本会话改动之上做的重命名。动LockScreenOverlay.java前必须先重新读文件。 - 「上滑解锁露壁纸一秒」「熄屏再亮屏先见原生锁屏一秒」两个现象真机复现仍未消除(README 第 7、9 条修复无效),本轮只加诊断日志、不改行为:
LockScreenOverlay.restore()改为restore(String reason)并打印状态快照、pre-draw 检测到解锁撤层打一次日志、render()每个早退分支走去重skip()、MediaSource.refresh()打印取图耗时(bitmap 直出 / 缓存帧 / URI 异步解码三条路径)、HookEntry打印根视图 attach/detach。抓取用adb -s 1b3a7d8 logcat -v time | grep MeloLock——覆盖层在com.android.systemui进程,配置端 App 进程的日志里不会有这些行。 - 首页大标题字号机制:Miuix
TopAppBar的大标题字号写死为textStyles.title1(32sp)且LocalTextStyles是 internal,因此HyperIslandTheme暴露LocalThemeController,CollapsingPage新增largeTitleFontSize(用同一 controller 再开一层MiuixTheme只换title1,不影响颜色/深浅色模式)。改名后Hyper MeloLock只有 12 字符,32sp 已能单行,首页不再传该参数,机制保留备用。 - 项目整体改名为 Hyper MeloLock:
app_name、Gradle 根项目名、包名与 applicationId(io.github.hypermusicscape.lock→io.github.melolock,含 7 个 Java 文件的package、Config.PACKAGE、assets/xposed_init、Manifest provider authorities)、日志 tag(MeloLock/ 配置端MeloLock[App])、图标源图已移到docs/images/icon.png与文档全部同步。改名后是另一个应用:不会覆盖升级旧包名,Vector 里要卸载旧模块并重新启用、重新勾选SystemUI作用域,旧配置不继承。仓库目录名仍是Hyper Music Scape Lock。 - Manifest 补声明
com.android.permission.GET_INSTALLED_APPS:申请未声明的权限系统会直接拒绝、不弹授权框,这是「音乐应用」页看不到授权框的原因;同时去掉「列表为空才申请」的额外条件,进入该页必申请。 - 首页系统信息对
SystemInfoProvider失败增加Build.*兜底与日志,实拍已恢复真值。 - 应用名与包名统一为
Hyper MeloLock/io.github.melolock,图标res/drawable-nodpi/ic_hmsc.png,开发者zuige/ GitHubzuige66。上一轮改名前的状态已提交f344d75 app页面配置。 - 音乐应用页改为列出全部已安装应用(进入时申请应用列表权限,另有「显示系统应用」过滤),由用户自行勾选要接管的播放器。
- 滑条点击行为改为直接跳到点击位置:
PreferenceSlider新增allowManualInput(默认true保持 HyperIsland 原行为,本模块传false不再弹手动输入框)。 - 禁用启动时的检查更新(
INTERNET已被移除,原本必然弹「检查更新失败」)。 - 修复首页状态卡关掉后点不回来:
OverviewStatusCard新增clickableWhenInactive(HyperIsland 原行为是未激活不可点)。 - 外观页新增锁屏三元素编辑器,并内置 OFL 圆体数字字体(Quicksand / Baloo 2)支持三档圆润 + 连续粗细。
- 直接迁入 HyperIsland(MIT)的配置端主题、组件、资源和导航,入口替换为:首页、音乐应用、外观、开发者。
- 四个根页面对齐 HyperIsland 版式:首页改成「状态卡 + 两张数据卡 + 系统信息卡 + 链接卡」;作者署名与外链留空显示「待填写」并置灰,位置在
LockScreenPages.kt末尾的TODO(作者信息)。 - 把
OverviewPage.kt私有的StatusGrid/StatusCard/StatCard/InfoCard/ 告警卡提升为internal并参数化标题,HyperIsland 首页与模块首页共用同一批组件。 Config.java新增ELEMENT_DEFAULTS与elementValues()/elementInt()/elementString()/setElementInt();ConfigProvider新增/elements的 key/value 查询;LockScreenOverlay.measureElements()负责算最终尺寸,新增roundTypeface()/applyClockTypeface()处理内置圆体字体与wght粗细。Config.java新增selectedPackages()、allPackagesDisabled()、enabledAppCount()、deviceSupported()只读辅助,用于区分「未设置(允许全部)」与「已全部取消」;未改动任何配置键。- 保留媒体播放器白名单和锁屏背景外观配置,并通过 ContentProvider 同步。
- 构建已升级至 Gradle 9.5 / AGP 9.3.1,并已用
./gradlew.bat --no-daemon :app:assembleDebug验证 Debug 构建通过、安装到真机并启动验证。 - 已禁用迁入代码的启动统计,并用 Manifest merger 移除最终 APK 的
INTERNET权限,避免访问 HyperIsland 更新/下载服务。
