Imported from WINGS-N/ePIN (
AGENTS.md). Install upstream withnpx skills add WINGS-N/ePIN. Copyright stays with the author.
AGENTS.md
Working guide for AI agents in the ePIN repository. Read this first. README.md is the
user-facing overview; this file is the build/commit/UI guide. When a rule here is marked HARD
RULE, follow it exactly - the user has corrected these before.
1. What this is
ePIN is a Java Android Xposed module for Samsung SystemUI (com.android.systemui) with a local
Samsung One UI (SESL 8) configuration app. It captures the lockscreen PIN, and when an entered
PIN matches a configured rule it runs a mapped destructive action (factory reset / remove Secure
Folder user) through a staged fallback ladder.
applicationId/namespace=wings.epin;compileSdk37,minSdk28,targetSdk36; Java 8 (sourceCompatibility/targetCompatibility1.8) inapp/build.gradle.- Single
:appmodule, 100% Java. Do NOT introduce Kotlin. - Two Xposed entry points install the same hooks; only one loads per framework (section 5).
2. Repo / module layout
Single Gradle module :app (settings.gradle). App packages under app/src/main/java/wings/epin/:
- root:
Main(legacy hook entry),PinConfigStore,PinBridgeReceiver,HeartbeatReceiver,HeartbeatAlert,HeartbeatCheckReceiver,WipeExecutor,WipeContinuationReceiver,BridgeContract,PinRule,PinAction,AboutAppActivity. core:PinMatchCore(framework-agnostic match logic),PinHasher,TestNotifier,AvatarDrawableFactory,Haptics.ui:MainActivityand theStatusFragment/PinCodesFragment/SettingsFragmenttabs.xposed:VectorModule(modern hook entry).
unica-mods/ holds Unica mod templates (priv-app system integration); the ePIN.apk copies
synced into them are build artifacts (gitignored). scripts/fetch_lsposed_payloads.py downloads
framework payloads (network; see section 4).
The SystemUI hook runs inside the com.android.systemui process; the config UI and the receivers
run in the wings.epin app process. Config lives in device-protected SharedPreferences that the
hook reads via a package context; the hook talks to the app only by one-way broadcasts (PIN
dispatch, heartbeat) - see PinMatchCore / BridgeContract. Liveness is a one-way heartbeat from
the hook plus an AlarmManager watchdog in the app (HeartbeatAlert) that shows an ongoing
"hook not responding" notification when heartbeats stop.
3. UI: SESL 8 + oneui-design (STRICT)
The UI uses Samsung SESL 8 forks of androidx/material plus io.github.tribalfs:oneui-design,
resolved from private GitHub Packages gated by seslUser / seslToken.
HARD RULE: never add a stock androidx.appcompat / fragment / preference / recyclerview /
core / com.google.android.material dependency. The configurations.configureEach { exclude }
block in app/build.gradle strips every stock androidx/material artifact so only the SESL forks
remain; the forks keep the same package names, so androidx.appcompat.app.AppCompatActivity
already IS the SESL one. Build screens with oneui-design widgets (ToolbarLayout, CardItemView,
TipsCard, BottomTabLayout, RoundedLinearLayout), copying existing screens.
Accent follows the device Material You palette on API 31+ via values-v31 / values-night-v31
(colorPrimary/colorAccent -> system_accent1_*). Do not hardcode accent colors; use
?attr/colorPrimary. The one deliberate fixed color is the destructive red (colors.xml
epin_danger) on the Remove button.
4. Build, run
- SESL access needs
seslUser/seslTokenset OUTSIDE the repo (gradle.propertiesin$HOME, orORG_GRADLE_PROJECT_seslUser/...seslToken, or envSESL_USER/SESL_TOKEN). Seesettings.gradle. - Debug APK:
./gradlew :app:assembleDebug. Signed release:./gradlew :app:assembleRelease(needskeystore.properties+keystore/epin.jks, both OUTSIDE git). The signed copy also lands atbuild/outputs/signed/ePIN-release-signed.apk. - Install + test:
adb installthe APK, enable the module in LSPosed/Vector with scope = System UI, then RESTART SystemUI - reinstalling does NOT reload the running hook. fetchLatestLsposedPayloads(scripts/fetch_lsposed_payloads.py) is a NETWORK step and is NOT on the normal build; it runs only frompackUnicaMods/packEpinLsposedMod. Force-skip with-PskipFetch.- CI (
.github/workflows/ci.yml, push/PR): JDK 21, one gate -./gradlew :app:assembleDebug --no-daemon. There is no lint/format/test tooling in this repo.release.ymlbuilds the signed release on a tag and publishes a GitHub Release.
5. Xposed entry points (dual, auto-fallback)
Two entry points install the SAME Keyguard hooks; only one loads per framework:
- modern:
META-INF/xposed/java_init.list->wings.epin.xposed.VectorModule(libxposed,compileOnly io.github.libxposed:api:102, Chain-styleHooker, no-arg constructor).module.propsetsminApiVersion=102. - legacy:
assets/xposed_init->wings.epin.Main(de.robv,app/libs/api-82.jarcompileOnly) plus thexposedmodulemeta in the manifest.
Auto-fallback is by minApiVersion: a framework with libxposed API >= 102 (current Vector) loads
the modern entry; anything older (older Vector on the annotation-style API, classic Xposed) skips
modern on the version gate and loads the legacy entry. Both funnel into
wings.epin.core.PinMatchCore, which has NO XposedBridge/XposedHelpers references
(android.util.Log + reflection only) so it is shared. A process/action dedup in PinMatchCore
prevents a double-fire if both ever load.
6. Behavior that must not regress (HARD RULES)
- Do not touch SystemUI lockscreen masking. PIN visibility toggles apply only to the app's own config fields.
- Save must not silently close the screen (toast confirmation). Do not persist an invalid rule: PIN length < 4, no action, or action with an empty PIN. An empty unused row is allowed.
- Destructive actions are real. Keep the TEST-mode switch working: with it on, a matched PIN only shows a notification and toast, never wipes.
- Root fallback stays user-controlled via the toggle and must verify
subefore staying enabled. - Keep scope targeting
com.android.systemui. Do not add destructive defaults. - Any new user-facing string/array goes in BOTH
values/andvalues-en/.
7. Code-text style (comments, logs, UI strings in source)
- ASCII only in anything written into the source tree:
-not an em/en-dash, straight"'not curly quotes,...not the ellipsis character, words not arrow glyphs. (User-facingstrings.xmlmay keep the ellipsis character where it reads better.) - No markdown in code comments: no backticks, no bold, no angle-bracket placeholders, no links.
Plain prose; name a symbol by writing its name. Javadoc
{@link ...}tags are fine. README and this file are markdown and stay markdown. - Comments only for genuinely non-obvious WHY (framework quirks, One UI compat, timing races). Do not restate what the code does; default to no comment.
8. Commit conventions
Format: [scope] short lowercase imperative - a single subject line, NO colon after the scope,
NO body, NO Co-Authored-By or AI-mention trailer. Pick the narrowest accurate scope:
[app/ui], [app/core], [app/xposed], [app/wipe], [docs], [build], [ci], [ignore].
Use bare [app] only for a change that truly spans many subsystems; split multi-scope work into
separate commits.
Do NOT git push without explicit user confirmation. Commit, then stop and report.
