Project
Biometric App Lock
Problem
Most Android “app lock” utilities are ad-supported, request network access, or treat a device PIN as good enough to reveal a lock list. I wanted a small, auditable locker that:
- Unlocks only with a strong biometric, not a PIN or pattern fallback inside the app
- Proves that unlock cryptographically, instead of just checking a boolean
- Keeps the lock list and telemetry off the network
- Fails closed when the sensor is missing, unenrolled, or unavailable
- Is honest about what an accessibility-service locker cannot stop
Design
Biometric App Lock covers selected apps until a class 3 (strong) biometric unlocks an Android Keystore AES key.
- Package / application id:
com.achillesrogan.applock - Platform: Android 8.0+ (minSdk 26), compiled and targeted at SDK 35
- Language: Kotlin
- Prompt: AndroidX
BiometricPromptwithBIOMETRIC_STRONGonly - Unlock path:
BiometricPrompt.CryptoObject→Cipher.doFinalmust succeed before the app proceeds - Key policy: authentication required on every use; enrolling or removing a biometric invalidates the key (
setInvalidatedByBiometricEnrollment) - Foreground detection: an accessibility service that listens for
TYPE_WINDOW_STATE_CHANGEDand reads the package name only (canRetrieveWindowContentis false) - Settings and lock list stay hidden until biometric auth succeeds
Version 1.3 is a rename from Fingerprint App Lock (1.2). The package id and signing certificate are unchanged, so 1.3 updates over 1.2 in place. Earlier 1.0/1.1 test builds used a different application id and remain separate installs.
Controls
| Control | Why it matters |
|---|---|
Keystore-bound CryptoObject | The UI cannot be satisfied by a stubbed “success” callback; the key operation has to work. |
| Fail closed | No enrolled strong biometric, or an unavailable sensor, means the settings list stays hidden and a locked app stays covered. |
FLAG_SECURE | Settings and lock windows are kept out of screenshots and Recents previews. |
filterTouchesWhenObscured | A touch passing through another window cannot flip a lock switch or press Unlock. |
No INTERNET permission | The manifest does not request network access. Nothing in the project uploads the lock list or the foreground package name. |
allowBackup="false" | Android Backup does not include the lock list. |
| Accessibility scope | The service does not read screen contents or keystrokes. Only the system can bind to it (BIND_ACCESSIBILITY_SERVICE). |
| Lock lifetime | An app is re-locked when it leaves the foreground or the screen turns off. “Currently unlocked” state is in memory only. |
Sideloaded APKs can be verified against the checksums published with the GitHub release. The 1.3 APK filename is biometricapplock-1.3-release.apk.
How it was tested
From the public project documentation and release process (implementation tests live in the app repo):
- Build and install on Android 8+ with an enrolled fingerprint; confirm the settings list is gated on biometric success.
- With no strong biometric enrolled, confirm the app stays on the explanation screen and does not expose the lock list.
- Enable the accessibility service, lock a target app, and confirm the cover appears and that Cancel/Back send the user home rather than into the app.
- Confirm a renamed 1.3 build upgrades 1.2 in place (same package id and signing key).
- Publish checksums on the GitHub Release and verify the downloaded APK against them.
The project README already documents the Android 13 restricted-settings step for sideloaded accessibility services.
Limitations (stated on purpose)
This is an accessibility-service locker, not a device-owner or device-admin control. The project README discloses the same limits:
- Safe mode does not run third-party accessibility services.
- ADB, root, or a user who can open Settings can disable the service or uninstall the app. Locking Settings raises the bar; it does not make removal impossible.
- The locked app can flash on screen for a moment before the cover is drawn.
- Recents may still show the locked app’s own last frame.
- Enrolling a new fingerprint invalidates the old key; the new key accepts currently enrolled strong biometrics.
- Google Play policy is a poor fit for accessibility-service lockers. Distribution today is sideload and personal use. IzzyOnDroid and F-Droid listings are in progress, not completed.
What I learned
- “Fingerprint required” is not the same as “Keystore key required.” Binding the prompt to a
CryptoObjectis the difference between a cosmetic gate and an auth-bound key. - Fail-closed UX is a security control. A locker that opens its settings when the sensor is down has already lost.
- Shipping a signed APK with checksums and a stable signing identity is part of the security story, especially when the app is sideloaded.
- Writing limitations down is not a weakness in a portfolio. It is how I would want an engineer to talk about a detection or a control in a SOC.
Links
- Source and README: github.com/achillesrogan/biometricapplock
- Release 1.3: github.com/achillesrogan/biometricapplock/releases/tag/1.3
- F-Droid / IzzyOnDroid listings are in progress; links will be added when a listing is actually public.