Biometrics
27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.
npx -y skills add piyushverma0/android-agent-skills --skill biometricsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 14 stars14 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Biometric authentication for Android AI agents. Use this skill whenever implementing fingerprint authentication, face unlock, biometric login, BiometricPrompt, BiometricManager, CryptoObject, Keystore encryption with biometrics, Class 2 vs Class 3 biometrics, biometric availability check, fallback to PIN/pattern, secure credential storage, or any biometric/fingerprint/face authentication flow in Android.
SKILL.md
5.6 KB, as published. Nobody here has run it
Biometric Authentication
Rule 1: Check availability before showing biometric UI
// ✅ Check what's available before prompting
fun BiometricManager.canAuthenticateWithBiometrics(): Boolean {
val authenticators = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
return canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
}
// ✅ Detailed availability check
sealed interface BiometricAvailability {
data object Available : BiometricAvailability
data object NoHardware : BiometricAvailability
data object NotEnrolled : BiometricAvailability
data object Unavailable : BiometricAvailability
}
fun checkBiometricAvailability(context: Context): BiometricAvailability {
val manager = BiometricManager.from(context)
return when (manager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS -> BiometricAvailability.Available
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricAvailability.NoHardware
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricAvailability.NotEnrolled
else -> BiometricAvailability.Unavailable
}
}
Rule 2: BiometricPrompt — correct setup in ComponentActivity
// ✅ Must be created in Activity, not in Composable
class MainActivity : ComponentActivity() {
private lateinit var biometricPrompt: BiometricPrompt
private lateinit var promptInfo: BiometricPrompt.PromptInfo
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupBiometrics()
setContent { MyApp(onAuthenticateClick = ::showBiometricPrompt) }
}
private fun setupBiometrics() {
val executor = ContextCompat.getMainExecutor(this)
biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
// Handle success — result.cryptoObject available if using CryptoObject
viewModel.onBiometricSuccess()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode != BiometricPrompt.ERROR_USER_CANCELED &&
errorCode != BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
viewModel.onBiometricError(errString.toString())
}
}
override fun onAuthenticationFailed() {
// Biometric recognized but not matched — DO NOT lock out here
// BiometricPrompt handles lockout automatically
}
})
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Use your biometric to unlock the app")
.setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
// Don't set setNegativeButtonText when DEVICE_CREDENTIAL is allowed
.build()
}
fun showBiometricPrompt() {
biometricPrompt.authenticate(promptInfo)
}
}
Rule 3: Crypto-backed biometrics for secure key operations
// ✅ Biometric-protected Keystore key
object BiometricCryptoHelper {
private const val KEY_NAME = "biometric_key"
fun generateKey() {
val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGen.init(
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
.setInvalidatedByBiometricEnrollment(true)
.build()
)
keyGen.generateKey()
}
fun getCipher(): Cipher {
val key = KeyStore.getInstance("AndroidKeyStore").run {
load(null)
getKey(KEY_NAME, null) as SecretKey
}
return Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}").apply {
init(Cipher.ENCRYPT_MODE, key)
}
}
}
// Authenticate with CryptoObject for maximum security
fun showSecureBiometricPrompt() {
val cipher = BiometricCryptoHelper.getCipher()
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
Common Mistakes
❌ Creating BiometricPrompt in Composable — create in Activity, pass lambda to Compose
❌ Setting setNegativeButtonText when DEVICE_CREDENTIAL is allowed — crash
❌ Not checking availability before showing prompt — shows broken UI on unsupported devices
❌ Locking user out on onAuthenticationFailed — BiometricPrompt handles lockout
❌ Using BIOMETRIC_WEAK for sensitive operations — use BIOMETRIC_STRONG with CryptoObject