Project

Biometric App Lock

Source: achillesrogan/biometricapplock. Current release: 1.3 (versionCode 4), published November 10, 2026, 12:08 PM CT. License: GPL-3.0-or-later. IzzyOnDroid and F-Droid listings are in progress, not completed.

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 BiometricPrompt with BIOMETRIC_STRONG only
  • Unlock path: BiometricPrompt.CryptoObject → Cipher.doFinal must 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_CHANGED and reads the package name only (canRetrieveWindowContent is 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 CryptoObject is 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