Common Biometric Login Bugs and How to Catch Them
Common Biometric Login Bugs and How to Catch Them
Common Biometric Login Bugs and How to Catch Them
Biometric login—fingerprint, face, iris, or voice—has become a default gate‑keeper for mobile and web apps. When it works, users glide past credentials; when it fails, frustration spikes, abandonment rises, and security teams scramble. This guide walks through the most frequent biometric‑login defects, explains why they appear, shows how they manifest to real people, and gives repeatable steps to reproduce, detect, fix, and prevent each one. The focus is on practical, engineer‑level tactics you can add to your CI pipeline today, plus a look at how persona‑driven autonomous exploration (like the kind SUSATest performs) surfaces issues that scripted checks often miss.
Common Biometric Login Bugs and How to Catch Them: Test Matrix Overview
| Bug ID | Symptom (User‑visible) | Root Cause | Detection Technique | Fix Strategy |
|---|---|---|---|---|
| B1 | Login button stays disabled after successful scan | Sensor API returns SUCCESS but UI state machine not updated | Instrument UI state + sensor callback logs | Guard UI transition with a debounced success flag |
| B2 | False‑negative in bright sunlight | Face‑match threshold too high for overexposed frames | Inject synthetic over‑exposed frames via camera mock | Adaptive threshold based on histogram analysis |
| B3 | Fingerprint sensor reports “busy” after device sleep | Power‑management driver fails to re‑initialize HAL | Power‑cycle + rapid re‑auth loop in test | Re‑init HAL on resume callback; add retry with back‑off |
| B4 | Voice‑print login accepts impostor with similar pitch | Feature extraction ignores spectral shape | Play recorded impostor voice with matched pitch | Add MFCC + liveness check (spectral flux) |
| B5 | Accessibility talk‑back reads “scanning” forever | Talk‑back not notified when scanning ends | Enable accessibility service + watch for announcements | Post accessibility event on scan completion |
| B6 | Biometric fallback to PIN shows raw error code | Error‑handling layer leaks internal codes | Trigger fallback by canceling biometric prompt | Map internal codes to user‑friendly messages |
| B7 | Concurrent biometric requests cause deadlock | Two threads lock the same sensor mutex | Stress test with parallel auth calls | Serialize requests via a singleton sensor manager |
| B8 | Iris sensor returns stale frame after device rotation | Camera preview not re‑configured on orientation change | Rotate device while holding gaze | Re‑start preview on orientation listener |
| B9 | Biometric login works on emulator but fails on real device | Emulator spoofs HAL with perfect data | Run same test on physical device fleet | Add device‑specific capability checks; avoid emulator‑only shortcuts |
| B10 | Login flow bypasses biometric consent dialog on Android 13+ | Manifest missing USE_BIOMETRIC permission | Verify consent dialog appears via UIAutomator | Declare permission; handle runtime request |
| B11 | Biometric token reused after password change | Token invalidation not tied to credential reset | Change password then attempt biometric login | Invalidate biometric token on any credential update |
| B12 | Biometric prompt appears behind system overlay (e.g., chat heads) | Window type not set to TYPE_APPLICATION_OVERLAYOUT_IN_DISPLAY | Show overlay then trigger biometric prompt | Use setShowForAllUsers(true) or appropriate window flags |
The table above captures a representative set of defects you will see in the wild. Each row maps a concrete user‑visible symptom to its underlying cause, a pragmatic way to catch it in testing, and a concrete remediation. The sections that follow unpack each pattern in depth, give reproducible steps, and show how automated and manual approaches complement each other.
Common Biometric Login Bugs and How to Catch Them: Fingerprint Sensor Timeout
Why it happens
Modern smartphones expose fingerprint authentication through a HAL that returns a result code within a configurable window (often 2–5 seconds). If the UI layer starts a timer that expires before the HAL callback, the login button may stay disabled or show a generic “try again” error even though the sensor succeeded. The bug is especially common on devices where OEMs customize the HAL timing or where power‑saving modes throttle the sensor driver.
User impact
The user places a finger, feels the vibration, sees the success animation, yet the app reports failure. Repeated attempts lead to lockout or fallback to PIN, eroding trust in the biometric option.
Reproduction steps (manual)
- Enable developer options → “Show taps” and “Show sensor debug”.
- Launch the app, navigate to the login screen.
- Place a registered finger on the sensor and hold it slightly longer than usual (≈ 6 seconds).
- Observe whether the UI transitions to the logged‑in state or stays at the prompt.
Automated detection
*Instrumentation test* (Espresso/AndroidJUnitRunner) that:
@Test
fun fingerprintTimeout_doesNotLockUi() {
// Mock the BiometricPrompt to return SUCCESS after 4 s
val mockBiometric = mock(BiometricPrompt::class.java)
`when`(mockBiometric.authenticate(any())).thenAnswer {
delay(4000)
BiometricPrompt.AuthenticationResult(
BiometricPrompt.CryptoObject(null),
BiometricPrompt.AuthenticationResult.SUCCESS
)
}
// Launch activity with mocked prompt
launchActivity<LoginActivity>()
// Click biometric button
onView(withId(R.id.btn_biometric)).perform(click())
// Verify that progress indicator disappears within 5 s
assertTrue(waitForId(R.id.progress_bar, 5000) { !isDisplayed() })
// Verify logged‑in state
assertTrue(waitForId(R.id.user_profile, 5000) { isDisplayed() })
}
A similar approach works with XCTest on iOS using LAContext mocks.
Fix & prevention
- Decouple UI state from raw HAL timing: set a flag
biometricSuccess = truein the callback, then let the UI react to that flag, not to a timer. - Add a small grace period (e.g., 500 ms) after receiving SUCCESS before disabling the prompt.
- In CI, run the test on a matrix of devices (including low‑end models) to catch OEM‑specific timing quirks.
Common Biometric Login Bugs and How to Catch Them: Face Recognition Lighting Issues
Why it happens
Face‑match algorithms rely on consistent illumination to compute similarity scores. When ambient light shifts dramatically—bright sunlight, back‑lit portraits, or low‑night modes—the captured frame may be over‑ or under‑exposed, pushing the feature vector outside the decision threshold. Many apps ship a static threshold tuned for indoor lighting, causing false negatives outdoors.
User impact
A user tries to unlock the phone while walking outside; the camera shows their face, but the login fails repeatedly. They may resort to PIN, which feels like a step backward.
Reproduction steps (manual)
- In a dark room, register a face.
- Move to a brightly lit outdoor setting (or use a photography light box).
- Attempt login; note failure rate.
- Repeat with varying angles and accessories (sunglasses, hats).
Automated detection
Using a camera mock that can inject synthetic frames:
@Test
public void faceLogin_underExposure_failsGracefully() {
// Create a dark frame (average pixel < 10)
Bitmap darkFrame = TestBitmapFactory.createUniform(10, 10, Color.BLACK);
// Inject into the face detection pipeline via dependency injection
faceDetector.setTestFrame(darkFrame);
// Trigger login
loginPresenter.startFaceAuth();
// Expect a specific error code, not a crash
assertEquals(FaceAuthResult.ERROR_LOW_LIGHT, loginResult.getCode());
}
A complementary test for over‑exposure uses a saturated white frame.
Fix & prevention
- Implement an adaptive threshold: compute the histogram of the preview frame; if the mean luminance falls outside
[30, 220](0‑255 scale), temporarily lower the match score requirement or request a retry with flash. - Add a liveness check that is illumination‑invariant (e.g., eye‑blink detection using infrared if available).
- In test suites, parametrize luminance levels and assert that the failure rate stays below a defined SLA (e.g., < 2 % false negative across 0‑255 luminance).
Common Biometric Login Bugs and How to Catch Them: Fingerprint Sensor Busy After Sleep
Why it happens
When the device enters sleep, the fingerprint HAL may be powered down to save battery. On resume, some OEM drivers fail to re‑initialize the sensor channel, leaving it in a “busy” state. The app’s biometric prompt then receives an error code (BIOMETRIC_ERROR_UNAVAILABLE) despite the hardware being functional.
User impact
After waking the phone, the user taps the fingerprint icon and sees an immediate error. Repeated attempts may trigger a lockout, forcing a PIN entry and creating a perception of unreliability.
Reproduction steps (manual)
- Register a fingerprint.
- Lock the device and let it sit for > 30 seconds to enter deep sleep.
- Wake the device with the power button.
- Immediately attempt fingerprint login.
- Observe error dialog.
Automated detection
Using Android’s adb shell to control power state:
# Ensure device is unlocked
adb shell input keyevent KEYCODE_WAKEUP
# Put device to sleep for 40s
adb shell input keyevent KEYCODE_POWER
sleep 40
adb shell input keyevent KEYCODE_POWER
# Launch app and trigger biometric
adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1
adb shell am start -n com.example.app/.LoginActivity
# Send a tap on the biometric button (coordinates from UIAutomator)
adb shell input tap 540 1800
# Capture logcat for BIOMETRIC_ERROR_UNAVAILABLE
adb logcat | grep BIOMETRIC_ERROR_UNAVAILABLE
In an Espresso test you can mock the PowerManager to invoke onResume() and then call the biometric API.
Fix & prevention
- Listen to
ACTION_SCREEN_ONandACTION_SCREEN_OFFbroadcasts; on screen‑on, callBiometricManager.canAuthenticate()to force a HAL re‑init. - Wrap the biometric call in a retry loop with exponential back‑off (max 3 attempts, 200 ms base).
- Add a unit test that simulates sleep/resume and asserts that the biometric API returns
SUCCESSwithin two attempts.
Common Biometric Login Bugs and How to Catch Them: Voice‑Print Pitch Spoof
Why it happens
Voice‑print systems often extract pitch‑based features (fundamental frequency, formant spacing) because they are computationally cheap. An attacker who can mimic the user’s pitch—using a voice‑changer app or a simple hum—can bypass the matcher if the system does not also examine spectral shape or dynamic traits.
User impact
A malicious actor gains access to a victim’s account without needing the password, leading to account takeover. The legitimate user may notice unauthorized activity only after the fact.
Reproduction steps (manual)
- Enroll a voiceprint using a passphrase (“my voice is my password”).
- Record a sample of the user’s voice.
- Use a pitch‑shifting tool (e.g., Audacity’s “Change Pitch”) to match the fundamental frequency while altering timbre.
- Play the modified audio through the device’s speaker at the login prompt.
- Verify whether login succeeds.
Automated detection
Create a test harness that feeds audio buffers to the voice‑auth module:
import numpy as np
import unittest
from voice_auth import VoiceAuthenticator
class VoiceSpoofTest(unittest.TestCase):
def setUp(self):
self.auth = VoiceAuthenticator(enroll_path="user_enroll.npz")
def test_pitch_only_spoof_fails(self):
# Generate a synthetic signal with same F0 but flat spectrum
t = np.linspace(0, 2, 48000*2)
f0 = 120.0 # Hz
signal = 0.5 * np.sin(2*np.pi*f0*t) # pure sine
# Normalize to match enrollment energy
signal = signal * np.sqrt(np.mean(self.auth.enroll_energy))
result = self.auth.authenticate(signal, sample_rate=48000)
self.assertFalse(result.is_success, "Pitch-only spoof should be rejected")
Fix & prevention
- Fuse pitch with mel‑frequency cepstral coefficients (MFCCs) and spectral flux; require a minimum distance in the combined feature space.
- Add a liveness test: prompt the user to speak a random phrase or perform a short breath‑sound challenge.
- In CI, run the spoof test against a library of common voice‑changer presets and assert a false‑accept rate < 0.1 %.
Common Biometric Login Bugs and How to Catch Them: Accessibility Talk‑Back Stuck
Why it happens
When the biometric prompt is displayed, the app often updates the UI to show a scanning animation. If the app fails to send an AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED or TYPE_ANNOUNCEMENT when the scan ends, TalkBack continues to read the previous “Scanning…” label, leaving the user unaware that the attempt finished.
User impact
A visually impaired user hears “Scanning…” indefinitely, cannot tell whether the attempt succeeded or failed, and may abandon the flow or repeatedly tap the button, causing lockout.
Reproduction steps (manual)
- Enable TalkBack in device settings.
- Navigate to the biometric login screen.
- Double‑tap to activate TalkBack focus on the scanning label.
- Trigger a biometric attempt (success or failure).
- Listen: does the spoken feedback change to “Success” or “Failed” after the animation ends?
Automated detection
Using UIAutomator and an AccessibilityService test probe:
@RunWith(AndroidJUnit4.class)
public class TalkBackBiometricTest {
@Test
public void scanningLabelUpdatesAfterAttempt() {
// Register a test AccessibilityService that captures events
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.executeShellCommand(
"settings put enabled_accessibility_services com.example.test/.TestAccessibilityService");
launchActivity<LoginActivity>();
onView(withId(R.id.btn_biometric)).perform(click());
// Wait for biometric result (mocked to succeed after 1s)
IdlingRegistry.getInstance().register(new CountingIdlingResource(2));
onView(withId(R.id.txt_scanning)).check(matches(withText(containsString("Success"))));
}
}
The custom service logs all AccessibilityEvent types; the test asserts that a TYPE_VIEW_TEXT_CHANGED with the success/failure string appears.
Fix & prevention
- After the biometric callback, explicitly call
announceForAccessibility(resultString)on the container view. - Ensure that any progress bar or spinner is marked
importantForAccessibility="no"so TalkBack does not read it as changing state. - Add an accessibility test suite that runs on each PR, using the Android Accessibility Test Framework (ATF) to verify state changes.
Common Biometric Login Bugs and How to Catch Them: Biometric Fallback Leaks Raw Error Codes
Why it happens
Many apps map the biometric API’s result codes directly to UI strings for speed. When the underlying HAL returns an OEM‑specific code (e.g., 7 for “sensor busy”), the UI shows that number instead of a helpful message, confusing users and exposing internal implementation details.
User impact
The user sees “Error 7” or “Status: -1008” and has no idea what to do next. This erodes confidence and may lead to support tickets.
Reproduction steps (manual)
- Force a biometric error by disabling the fingerprint sensor via developer options (“Fingerprint sensor → Disabled”).
- Attempt login.
- Observe the error dialog text.
Automated detection
Create a resource‑checking test that verifies all possible biometric error codes are mapped to strings in strings.xml:
@Test
fun biometricErrorCodesHaveUserFriendlyStrings() {
val errorCodes = listOf(
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
BiometricManager.BIOMETRIC_ERROR_NO_BIOMETRICS,
BiometricManager.BIOMETRIC_ERROR_LOCKOUT,
BiometricManager.BIOMETRIC_ERROR_TIMEOUT,
// add OEM-specific codes as needed
)
val resources = ApplicationProvider.getApplicationContext().resources
for (code in errorCodes) {
val stringId = resources.getIdentifier("biometric_error_$code", "string",
ApplicationProvider.getApplicationContext().packageName)
assertTrue(stringId != 0, "Missing string resource for error code $code")
val text = resources.getString(stringId)
assertFalse(text.contains(String.valueOf(code)),
"String for error $code still contains the raw number")
}
}
Fix & prevention
- Maintain a lookup table that maps every possible
BiometricError(including vendor extensions) to a user‑friendly message. - Use
BiometricManager.canAuthenticate()to pre‑check availability and show a proactive hint (“Set up fingerprint in Settings”). - In CI, run the above test on every build; treat missing strings as a failure.
Common Biometric Login Bugs and How to Catch Them: Concurrent Biometric Requests Cause Deadlock
Why it happens
If two UI components (e.g., a login fragment and a settings fragment) both request biometric authentication simultaneously, each may acquire a lock on the HAL’s singleton service. If the HAL implementation uses a non‑reentrant mutex and does not queue requests, the second caller blocks forever, waiting for the first to release a lock it never will (because the first is waiting for the UI to update).
User impact
The app appears frozen after tapping the biometric button; no error is shown, and the user must force‑close the app.
Reproduction steps (manual)
- Open the login screen.
- Without completing the biometric flow, navigate to a settings screen that also offers biometric verification (e.g., for enabling fingerprint unlock).
- Rapidly trigger biometric authentication on both screens (using two fingers or a helper tool).
- Observe whether the UI becomes unresponsive.
Automated detection
Using Espresso’s IdlingResource to monitor HAL lock state via a custom wrapper:
public class BiometricLockIdlingResource implements IdlingResource {
private volatile boolean idle = true;
@Override
public String getName() { return BiometricLockIdlingResource.class.getName(); }
@Override
public boolean isIdleNow() { return idle; }
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) { this.callback = callback; }
// Called by the test double that wraps BiometricPrompt
public void setLockState(boolean locked) {
idle = !locked;
if (!idle && callback != null) callback.onTransitionToIdle();
}
}
In the test:
@Rule public IdlingResourceRule rule = new IdlingResourceRule(new BiometricLockIdlingResource());
@Test
public void concurrentRequestsDoNotDeadlock() {
launchActivity<LoginActivity>();
// First request
onView(withId(R.id.btn_biometric_login)).perform(click());
// Second request from settings (navigate then click)
onView(withId(R.id.nav_settings)).perform(click());
onView(withId(R.id.btn_biometric_settings)).perform(click());
// Expect both to complete within 5s
onView(withId(R.id.user_profile)).check(matches(isDisplayed()));
onView(withId(R.id.settings_saved)).check(matches(isDisplayed()));
}
If the test hangs past the timeout, the IdlingResource never reports idle, flagging a deadlock.
Fix & prevention
- Serialize all biometric requests through a singleton
BiometricRequestQueuethat uses aLinkedBlockingDequeand processes one request at a time. - Use a timeout on the HAL call; if it exceeds a threshold, return an error and allow the UI to retry.
- Add a unit test that simulates concurrent calls and asserts that the queue processes them sequentially without exceeding a max latency (e.g., 3 seconds per request).
Common Biometric Login Bugs and How to Catch Them: Iris Sensor Stale Frame After Rotation
Why it happens
The iris HAL often ties the preview surface to the display orientation. When the device rotates, the preview may not be restarted, leaving the last frame buffered. The matcher then compares a stale iris image with the live template, resulting in a mismatch.
User impact
A user unlocks the phone while switching from portrait to landscape; the iris scanner fails repeatedly, forcing a fallback to PIN and causing frustration.
Reproduction steps (manual)
- Enroll iris in portrait mode.
- Rotate device to landscape while keeping gaze on the sensor.
- Attempt iris login.
- Note failure rate.
Automated detection
Using Android’s Camera2 API to inject a fake preview that reports a fixed timestamp:
@Test
public void irisLogin_afterRotation_usesFreshFrame() {
// Mock the camera device to return a frame with timestamp = System.nanoTime() - 500ms (stale)
when(mockCameraDevice.createCaptureSession(any(), any(), any()))
.thenAnswer(invocation -> {
CaptureSessionStub stub = new CaptureSessionStub();
stub.setFrameTimestamp(System.nanoTime() - 500_000_000); // 0.5 s stale
return stub;
});
// Trigger login
loginPresenter.startIrisAuth();
// Verify that the matcher requested a new frame (timestamp within 100ms)
verify(mockMatcher).authenticate(argThat(frame ->
System.nanoTime() - frame.timestampNs < 100_000_000));
}
Fix & prevention
- In the
OrientationEventListener, stop and restart the camera preview on each change. - Attach a timestamp to each frame and reject any frame older than a configurable threshold (e.g., 150 ms).
- Test the behavior on a device lab that includes multiple OEMs, as some vendors handle orientation differently.
Common Biometric Login Bugs and How to Catch Them: Biometric Login Works on Emulator but Fails on Real Device
Why it happens
Emulators typically provide a perfect, deterministic biometric HAL that always returns SUCCESS with synthetic data. Real devices have noise, sensor variance, power‑management quirks, and OEM customizations that the emulator does not replicate.
User impact
A feature passes all CI tests (run on emulators) but receives complaints in the field, leading to hot‑fixes and delayed releases.
Reproduction steps (manual)
- Run the app’s biometric login flow on an Android emulator with fingerprint enabled.
- Verify success.
- Flash the same build onto a physical device (mid‑range and high‑end).
- Attempt login with a registered finger.
- Compare success/failure rates.
Automated detection
Create a device‑farm test that runs the same instrumentation suite on both an emulator and a set of real devices:
pipeline {
agent any
stages {
stage('Emulator Test') {
steps {
sh '''adb -e emu kill
avdmanager create avd -n test -k "system-images;android-33;google_apis;x86_64"
emulator -avd test -no-window -no-audio &
sleep 30
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.targetInstrumentation=com.example.test
'''
}
}
stage('Real Device Test') {
steps {
sh '''adb devices | grep -v List | while read line; do
device=$(echo $line | awk '{print $1}')
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.targetDevice=$device
done'''
}
}
}
If the emulator stage passes but any real‑device stage fails, the pipeline is marked unstable.
Fix & prevention
- Never rely on emulator‑only shortcuts (e.g., hard‑coding
BiometricManager.STRONG). - Use feature flags that disable biometric login on devices where
BiometricManager.canAuthenticate()returns false for the desired modality. - Include a “device‑specific quirks” checklist in your release process: verify fingerprint, face, iris, and voice on at least three distinct hardware tiers.
Common Biometric Login Bugs and How to Catch Them: Biometric Token Reused After Password Change
Why it happens
Some apps store a cryptographic token (or a key tied to the biometric authenticator) in the Keystore/Keychain and reuse it for subsequent logins. When the user changes their password, the app may not invalidate or re‑derive that token, allowing an attacker who knows the old password to still unlock the app via biometrics.
User impact
A compromised password does not necessarily revoke biometric access, extending the window of unauthorized entry.
Reproduction steps (manual)
- Log in with password + biometric (enrolls token).
- Change the password via the app’s settings.
- Log out.
- Attempt login using only biometrics (no password).
- Verify whether access is granted.
Automated detection
Using a mock Keystore that logs token usage:
@Test
fun biometricTokenInvalidatedOnPasswordChange() {
val keystore = mockKeystore()
val auth = BiometricAuthenticator(keystore)
// Initial login
auth.loginWithBiometric(enrolledFingerprint)
verify(keystore).useToken(eq(BIOMETRIC_TOKEN_ID))
// Change password
auth.changePassword("oldPwd", "newPwd")
// Attempt biometric login again
auth.loginWithBiometric(enrolledFingerprint)
// Expect that the keystore asks for a new token (re‑authentication)
verify(keystore, times(2)).generateNewToken(any())
}
Fix & prevention
- Tie the biometric‑protected key to a version number or password hash; on password change, increment the version and delete the old key.
- Require re‑authentication with the new password before allowing biometric login again.
- Add a unit test that asserts the old token is no longer usable after a password change.
Common Biometric Login Bugs and How to Catch Them: Biometric Prompt Appears Behind System Overlay
Why it happens
On Android, windows have types and flags. If the biometric prompt is launched with a window type that is lower than a system overlay (e.g., a chat head from a messenger app), the overlay can cover the prompt, making the button invisible or unresponsive.
User impact
The user sees the biometric dialog dimmed or half‑hidden, taps where they think the button is, and gets no response. They may think the sensor is broken.
Reproduction steps (manual)
- Install an app that creates a persistent overlay (e.g., a floating widget).
- Enable the overlay and position it over the center of the screen.
- Open the target app and navigate to the biometric login screen.
- Trigger the biometric prompt.
- Observe whether the prompt is fully visible or obscured.
Automated detection
Using UIAutomator to check the window layer:
@Test
public void biometricPromptNotCoveredByOverlay() {
// Add an overlay view via WindowManager
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
View overlay = LayoutInflater.from(getContext()).inflate(R.layout.overlay_view, null);
wm.addView(overlay, layoutParamsOverlay);
// Launch biometric prompt
launchActivity<LoginActivity>();
onView(withId(R.id.btn_biometric)).perform(click());
// Get the window token of the prompt
IBinder promptToken = getWindowToken(R.id.biometric_prompt_container);
IBinder overlayToken = getWindowToken(R.id.overlay_view);
// Ensure prompt token is above overlay token in Z‑order
assertTrue(isWindowAbove(promptToken, overlayToken));
}
Fix & prevention
- Use
BiometricPromptwhich automatically creates a window with typeTYPE_APPLICATION_OVERLAY(or the appropriate fallback) ensuring it appears above most app windows but below critical system panels. - If you must use a custom dialog, set
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)and add the flagFLAG_NOT_FOCUSABLEonly when needed. - Test on devices with known problematic overlays (e.g., Facebook Messenger heads, Samsung Edge panels).
Common Biometric Login Bugs and How to Catch Them: Checklist for Release
| ✅ Item | Description | How to Verify |
|---|---|---|
| 1 | Biometric success callback updates UI state flag, not just a timer | Unit test with mocked HAL delay > 5 s |
| 2 | Face matcher adapts to luminance extremes (0‑255) | Parameterized test with synthetic dark/bright frames |
| 3 | Fingerprint HAL re‑initialized on ACTION_SCREEN_ON | Power‑cycle test + log check for HAL init |
| 4 | Voice auth rejects pitch‑only spoofs | Synthetic audio test with matched F0, varied timbre |
| 5 | Accessibility events fire on scan start/complete | TalkBack test probe + assertion on announcement strings |
| 6 | All biometric error codes map to user‑friendly strings | Resource‑lookup test (see table) |
| 7 | Biometric requests are serialized via a queue | Concurrency test with IdlingResource |
| 8 | Iris preview restarts on orientation change | Camera2 mock + timestamp verification |
| 9 | Real‑device behavior matches emulator baseline | Device‑farm pipeline comparison |
| 10 | Biometric token invalidated on password change | Keystore mock test + token usage verification |
| 11 | Prompt window appears above system overlays | Overlay test + window‑order check |
| 12 | No hard‑coded OEM error numbers in UI | String‑resource audit (regex for \d+) |
Run this checklist as part of your pre‑release gate; any failed item blocks the merge.
Common Biometric Login Bugs and How to Catch Them: How Persona‑Driven Autonomous Exploration Finds What Scripts Miss
Scripted tests excel at checking predefined paths, but biometric login bugs often surface only under atypical user behaviors—hesitant fingers, rapid re‑tries, off‑angle glances, or environmental distractions. Autonomous agents that simulate a variety of personas (curious, impatient, novice, adversarial, elderly, accessibility‑seeking, power‑user) can discover these edge cases by:
- Varying interaction timing – Anchors** – The agent may hold a finger longer than the typical 800 ms, tap repeatedly, or lift early, exposing timeout or debounce flaws.
- Changing Environmental Context – By adjusting virtual lighting, background noise, or simulated motion, the agent can trigger face‑ or iris‑matching failures that a static lab script never sees.
- Mixing Modalities – A power‑user persona might attempt to enroll multiple fingers while simultaneously switching apps, revealing race conditions in the biometric manager.
- Accessibility‑Focused Runs – An elderly or low‑vision persona drives the agent to rely on TalkBack, uncovering missing announcements or mis‑ordered focus.
5.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free