{"id":"21fb04fd-cb84-4a2b-8271-c95e4834e5b2","shortId":"vsBkht","kind":"skill","title":"biometrics","tagline":"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.","description":"# Biometric Authentication\r\n\r\n## Rule 1: Check availability before showing biometric UI\r\n\r\n```kotlin\r\n// ✅ Check what's available before prompting\r\nfun BiometricManager.canAuthenticateWithBiometrics(): Boolean {\r\n    val authenticators = BIOMETRIC_STRONG or DEVICE_CREDENTIAL\r\n    return canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS\r\n}\r\n\r\n// ✅ Detailed availability check\r\nsealed interface BiometricAvailability {\r\n    data object Available : BiometricAvailability\r\n    data object NoHardware : BiometricAvailability\r\n    data object NotEnrolled : BiometricAvailability\r\n    data object Unavailable : BiometricAvailability\r\n}\r\n\r\nfun checkBiometricAvailability(context: Context): BiometricAvailability {\r\n    val manager = BiometricManager.from(context)\r\n    return when (manager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {\r\n        BiometricManager.BIOMETRIC_SUCCESS -> BiometricAvailability.Available\r\n        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,\r\n        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricAvailability.NoHardware\r\n        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricAvailability.NotEnrolled\r\n        else -> BiometricAvailability.Unavailable\r\n    }\r\n}\r\n```\r\n\r\n## Rule 2: BiometricPrompt — correct setup in ComponentActivity\r\n\r\n```kotlin\r\n// ✅ Must be created in Activity, not in Composable\r\nclass MainActivity : ComponentActivity() {\r\n    private lateinit var biometricPrompt: BiometricPrompt\r\n    private lateinit var promptInfo: BiometricPrompt.PromptInfo\r\n\r\n    override fun onCreate(savedInstanceState: Bundle?) {\r\n        super.onCreate(savedInstanceState)\r\n        setupBiometrics()\r\n        setContent { MyApp(onAuthenticateClick = ::showBiometricPrompt) }\r\n    }\r\n\r\n    private fun setupBiometrics() {\r\n        val executor = ContextCompat.getMainExecutor(this)\r\n        biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {\r\n            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {\r\n                // Handle success — result.cryptoObject available if using CryptoObject\r\n                viewModel.onBiometricSuccess()\r\n            }\r\n            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {\r\n                if (errorCode != BiometricPrompt.ERROR_USER_CANCELED &&\r\n                    errorCode != BiometricPrompt.ERROR_NEGATIVE_BUTTON) {\r\n                    viewModel.onBiometricError(errString.toString())\r\n                }\r\n            }\r\n            override fun onAuthenticationFailed() {\r\n                // Biometric recognized but not matched — DO NOT lock out here\r\n                // BiometricPrompt handles lockout automatically\r\n            }\r\n        })\r\n\r\n        promptInfo = BiometricPrompt.PromptInfo.Builder()\r\n            .setTitle(\"Authenticate\")\r\n            .setSubtitle(\"Use your biometric to unlock the app\")\r\n            .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)\r\n            // Don't set setNegativeButtonText when DEVICE_CREDENTIAL is allowed\r\n            .build()\r\n    }\r\n\r\n    fun showBiometricPrompt() {\r\n        biometricPrompt.authenticate(promptInfo)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 3: Crypto-backed biometrics for secure key operations\r\n\r\n```kotlin\r\n// ✅ Biometric-protected Keystore key\r\nobject BiometricCryptoHelper {\r\n    private const val KEY_NAME = \"biometric_key\"\r\n\r\n    fun generateKey() {\r\n        val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, \"AndroidKeyStore\")\r\n        keyGen.init(\r\n            KeyGenParameterSpec.Builder(KEY_NAME,\r\n                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)\r\n                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)\r\n                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)\r\n                .setUserAuthenticationRequired(true)\r\n                .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)\r\n                .setInvalidatedByBiometricEnrollment(true)\r\n                .build()\r\n        )\r\n        keyGen.generateKey()\r\n    }\r\n\r\n    fun getCipher(): Cipher {\r\n        val key = KeyStore.getInstance(\"AndroidKeyStore\").run {\r\n            load(null)\r\n            getKey(KEY_NAME, null) as SecretKey\r\n        }\r\n        return Cipher.getInstance(\"${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}\").apply {\r\n            init(Cipher.ENCRYPT_MODE, key)\r\n        }\r\n    }\r\n}\r\n\r\n// Authenticate with CryptoObject for maximum security\r\nfun showSecureBiometricPrompt() {\r\n    val cipher = BiometricCryptoHelper.getCipher()\r\n    biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ Creating BiometricPrompt in Composable — create in Activity, pass lambda to Compose\r\n❌ Setting `setNegativeButtonText` when `DEVICE_CREDENTIAL` is allowed — crash\r\n❌ Not checking availability before showing prompt — shows broken UI on unsupported devices\r\n❌ Locking user out on `onAuthenticationFailed` — BiometricPrompt handles lockout\r\n❌ Using `BIOMETRIC_WEAK` for sensitive operations — use `BIOMETRIC_STRONG` with CryptoObject","tags":["biometrics","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-biometrics","topic-agent-skills","topic-ai-agent","topic-android","topic-antigravity","topic-claude-code","topic-codex","topic-cursor","topic-gemini-cli","topic-hilt","topic-jetpack-compose","topic-kotlin","topic-material3"],"categories":["android-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/piyushverma0/android-agent-skills/biometrics","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add piyushverma0/android-agent-skills","source_repo":"https://github.com/piyushverma0/android-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (5,243 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:08.958Z","embedding":null,"createdAt":"2026-05-18T13:14:48.422Z","updatedAt":"2026-05-18T19:09:08.958Z","lastSeenAt":"2026-05-18T19:09:08.958Z","tsv":"'0':313 '1':38 '18':33 '2':126 '27':2 '3':260 '5':28 'activ':137,376 'ae':291,341 'agent':7 'ai':6,30 'algorithm':290,340 'allow':253,387 'android':3 'androidkeystor':292,327 'app':238 'appli':348 'auth':14 'authent':36,56,64,230,353 'automat':226 'avail':40,49,68,75,187,391 'back':263 'bill':27 'biometr':1,35,43,57,101,213,234,240,264,271,282,315,410,416 'biometric-protect':270 'biometricavail':72,76,80,84,88,93 'biometricavailability.available':108 'biometricavailability.nohardware':117 'biometricavailability.notenrolled':122 'biometricavailability.unavailable':124 'biometriccryptohelp':276 'biometriccryptohelper.getcipher':363 'biometricmanager.biometric':65,106,109,113,118 'biometricmanager.canauthenticatewithbiometrics':53 'biometricmanager.from':96 'biometricprompt':127,147,148,173,174,223,371,406 'biometricprompt.authenticate':257,364 'biometricprompt.authenticationcallback':178 'biometricprompt.authenticationresult':183 'biometricprompt.cryptoobject':366 'biometricprompt.error':201,205 'biometricprompt.promptinfo':153 'biometricprompt.promptinfo.builder':228 'boolean':54 'broken':396 'build':254,319 'bundl':158 'button':207 'canauthent':63 'cancel':203 'cbc':305,344 'charsequ':198 'check':39,46,69,390 'checkbiometricavail':90 'cipher':323,362,367 'cipher.encrypt':350 'cipher.getinstance':338 'class':141 'claud':8 'code':9 'codex':10 'common':368 'componentact':131,143 'compos':140,373,380 'const':278 'context':91,92,97 'contextcompat.getmainexecutor':171 'correct':128 'crash':388 'creat':135,370,374 'credenti':61,105,244,251,385 'crypto':262 'crypto-back':261 'cryptoobject':190,355,419 'cursor':11 'data':73,77,81,85 'day':34 'decrypt':301 'design':17 'detail':67 'devic':60,104,243,250,384,400 'els':123 'encrypt':298 'enrol':121 'error':16,110,114,119 'errorcod':195,200,204 'errstr':197 'errstring.tostring':209 'executor':170,176 'fitgenz':29 'fix':12 'fun':52,89,155,167,180,193,211,255,284,321,359 'generatekey':285 'getciph':322 'getkey':331 'handl':184,224,407 'hardwar':112 'hilt':15 'hw':115 'inconsist':18 'init':349 'int':196 'interfac':71 'kapt':19 'key':267,274,280,283,295,325,332,352 'keygen':287 'keygen.generatekey':320 'keygen.init':293 'keygenerator.getinstance':288 'keygenparameterspec.builder':294 'keyproperties.auth':314 'keyproperties.block':303,342 'keyproperties.encryption':307,345 'keyproperties.key':289,339 'keyproperties.purpose':297,300 'keystor':273 'keystore.getinstance':326 'kotlin':45,132,269 'ksp':20 'lambda':378 'lateinit':145,150 'load':329 'lock':220,401 'lockout':225,408 'mainact':142 'manag':95 'manager.canauthenticate':100 'match':217 'maximum':357 'miss':21 'mistak':369 'mode':304,343,351 'must':133 'myapp':163 'name':281,296,333 'negat':206 'nohardwar':79 'none':120 'notenrol':83 'null':330,334 'object':74,78,82,86,177,275 'onauthenticateclick':164 'onauthenticationerror':194 'onauthenticationfail':212,405 'onauthenticationsucceed':181 'oncreat':156 'oper':268,414 'overrid':154,179,192,210 'pad':308,346 'pass':377 'pkcs7':309,347 'privat':144,149,166,277 'prompt':51,394 'promptinfo':152,227,258,365 'protect':272 'recogn':214 'reduc':24 'result':182 'result.cryptoobject':186 'return':62,98,337 'rule':37,125,259 'run':328 'savedinstancest':157,160 'seal':70 'secretkey':336 'secur':266,358 'sensit':413 'set':247,381 'setallowedauthent':239 'setblockmod':302 'setcont':162 'setencryptionpad':306 'setinvalidatedbybiometricenrol':317 'setnegativebuttontext':248,382 'setsubtitl':231 'settitl':229 'setup':129 'setupbiometr':161,168 'setuserauthenticationparamet':312 'setuserauthenticationrequir':310 'ship':31 'show':42,393,395 'showbiometricprompt':165,256 'showsecurebiometricprompt':360 'skill':4 'skill-biometrics' 'source-piyushverma0' 'state':23 'strong':58,102,241,316,417 'success':66,107,185 'supabas':13 'super.oncreate':159 'token':26 'topic-agent-skills' 'topic-ai-agent' 'topic-android' 'topic-antigravity' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-gemini-cli' 'topic-hilt' 'topic-jetpack-compose' 'topic-kotlin' 'topic-material3' 'true':311,318 'ui':44,397 'uistat':22 'unavail':87,116 'unlock':236 'unsupport':399 'use':189,232,409,415 'user':202,402 'val':55,94,169,279,286,324,361 'var':146,151 'viewmodel.onbiometricerror':208 'viewmodel.onbiometricsuccess':191 'weak':411","prices":[{"id":"ec45dddc-fb4b-48fd-82ed-31b96466d71c","listingId":"21fb04fd-cb84-4a2b-8271-c95e4834e5b2","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"piyushverma0","category":"android-agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:48.422Z"}],"sources":[{"listingId":"21fb04fd-cb84-4a2b-8271-c95e4834e5b2","source":"github","sourceId":"piyushverma0/android-agent-skills/biometrics","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/biometrics","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:48.422Z","lastSeenAt":"2026-05-18T19:09:08.958Z"}],"details":{"listingId":"21fb04fd-cb84-4a2b-8271-c95e4834e5b2","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"biometrics","github":{"repo":"piyushverma0/android-agent-skills","stars":8,"topics":["agent-skills","ai-agent","android","antigravity","claude-code","codex","cursor","gemini-cli","hilt","jetpack-compose","kotlin","material3","open-source","skills","supabase"],"license":"mit","html_url":"https://github.com/piyushverma0/android-agent-skills","pushed_at":"2026-04-27T09:15:31Z","description":"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.","skill_md_sha":"76390db8de2185d054072f92adc73906197aa8e7","skill_md_path":"skills/biometrics/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/biometrics"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/biometrics"},"updatedAt":"2026-05-18T19:09:08.958Z"}}