CVE-2026-0017 AI-generated analysis
platform/frameworks/base (services/core biometrics, BiometricService.SettingObserver) and platform/packages/apps/Settings (biometrics/face and biometrics/fingerprint preference controllers)
When the new per-modality biometric settings keys (FACE_/FINGERPRINT_KEYGUARD_ENABLED, FACE_/FINGERPRINT_APP_ENABLED) are unset after an OTA, BiometricService and the Settings preference controllers now inherit the value of the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys instead of falling back to the hard-coded default.
Root cause
In BiometricService.SettingObserver, the uri-keyed settings change handler read Settings.Secure.FACE_KEYGUARD_ENABLED, FINGERPRINT_KEYGUARD_ENABLED, FACE_APP_ENABLED and FINGERPRINT_APP_ENABLED with the fixed default DEFAULT_KEYGUARD_ENABLED ? 1 : 0 or DEFAULT_APP_ENABLED ? 1 : 0. The four Settings controllers (FaceSettingsAppsPreferenceController, FaceSettingsKeyguardUnlockPreferenceController, FingerprintSettingsAppsPreferenceController, FingerprintSettingsKeyguardUnlockPreferenceController) also read them with DEFAULT = ON. On a device updated by OTA where only the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED keys were set, the new keys are absent. A user who had turned biometric keyguard unlock (or app use) off then had it treated as the default after the update. This affected both the service state (mFaceEnabledOnKeyguard, mFingerprintEnabledOnKeyguard, mFaceEnabledForApps, mFingerprintEnabledForApps) and the Settings UI toggle. The commit title describes this as 'Unlock your phone' unexpectedly turned ON after OTA.
How the fix works
- platform/frameworks/base cea235f00865, services/core/java/com/android/server/biometrics/BiometricService.java, SettingObserver uri-keyed change handler: in the FACE_KEYGUARD_ENABLED and FINGERPRINT_KEYGUARD_ENABLED branches, the code first reads Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED with default -1. defaultValue is DEFAULT_KEYGUARD_ENABLED if the legacy key is unset (-1), otherwise (legacy == 1). That defaultValue replaces the constant default when the new key is read into mFaceEnabledOnKeyguard / mFingerprintEnabledOnKeyguard.
- Same file, FACE_APP_ENABLED and FINGERPRINT_APP_ENABLED branches: the code reads Settings.Secure.BIOMETRIC_APP_ENABLED with default -1 and derives defaultValue the same way (DEFAULT_APP_ENABLED if unset, else legacy == 1) before reading the new key into mFaceEnabledForApps / mFingerprintEnabledForApps. The service change only affects reads; it does not write the new keys.
- platform/packages/apps/Settings 7d8fbee887fc, constructors of FaceSettingsAppsPreferenceController, FaceSettingsKeyguardUnlockPreferenceController, FingerprintSettingsAppsPreferenceController and FingerprintSettingsKeyguardUnlockPreferenceController: the fix adds NOT_SET = -1 and reads the new key and the matching legacy key (BIOMETRIC_APP_ENABLED or BIOMETRIC_KEYGUARD_ENABLED) with default NOT_SET. If the new key is NOT_SET and the legacy key is not, Settings.Secure.putIntForUser writes the legacy value into the new key for getUserId(). isChecked() is only reformatted and still reads the new key with DEFAULT = ON.
- Tests: BiometricServiceTest gains four tests (gated on com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) asserting getEnabledForApps / getEnabledOnKeyguard return false when the legacy key is 0 and the new key is unset. Four new Robolectric controller tests assert isChecked() is false in the same situation and true when the new key is explicitly 1.
Completeness
- All four new per-modality keys are handled in both the service observer and the Settings controllers, each mapped to its matching legacy key (keyguard -> BIOMETRIC_KEYGUARD_ENABLED, app -> BIOMETRIC_APP_ENABLED). The comments in the two keyguard branches of BiometricService mention BIOMETRIC_APP_ENABLED but the code reads BIOMETRIC_KEYGUARD_ENABLED, so the code is correct and only the comment is wrong.
- The BiometricService change applies only inside the SettingObserver change handler for those URIs. The material does not show whether the maps are first filled at service start through this same handler.
- The service side never persists the migrated value. Persistence happens only when one of the four Settings preference controllers is constructed. Until then the service relies on the default it computes each time the handler reads the key.
- In BiometricService the legacy value is mapped with 'biometricKeyguardEnabled == 1' / 'biometricAppEnabled == 1', so any legacy value other than 1 (not only 0) yields disabled. The Settings controllers copy the raw legacy int instead.
- The Settings controllers use -1 as the sentinel for 'not set', so a stored explicit -1 cannot be told apart from an absent key. The new tests rely on exactly this by writing -1.
Behavior changes
- BiometricService: on devices where FACE_/FINGERPRINT_KEYGUARD_ENABLED or FACE_/FINGERPRINT_APP_ENABLED is unset, getEnabledOnKeyguard / getEnabledForApps now follow the legacy BIOMETRIC_KEYGUARD_ENABLED / BIOMETRIC_APP_ENABLED value instead of the constant default.
- Settings app: constructing any of the four face/fingerprint toggle controllers now does a one-time Settings.Secure.putIntForUser of the new key when it is unset and the legacy key is set. The new key is then persisted and the toggle shows the pre-OTA choice.
- No change when the new keys are already set, or when neither the legacy nor the new key exists (the hard-coded defaults still apply).
Per major
- Android 14: not affected per the bulletin; at android-security-14.0.0_r29 only 3/8 lines of the patched region are present.
- Android 15: not affected per the bulletin; at android-security-15.0.0_r17 only 3/8 lines of the patched region are present.
- Android 16: same fix: platform/frameworks/base cea235f00865 and platform/packages/apps/Settings 7d8fbee887fc in android-security-16.0.0_r4 (cherry-picks of a3799e443e9f and 05f0884146a0); 8/8 lines of the patched region present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin (not listed); 8/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- Whether BiometricService fills mFaceEnabledOnKeyguard and the other maps at startup through the same change handler, or through a separate initial read that would still use the constant default.
- The exact name of the enclosing SettingObserver method; the hunks show only its uri-keyed branches.
- The values of DEFAULT_KEYGUARD_ENABLED and DEFAULT_APP_ENABLED in BiometricService.
- Whether any other migration (for example a SettingsProvider upgrade step) writes the new per-modality keys during OTA; the material shows none.
- When setUserId() is called on the controllers relative to construction. The migration uses getUserId() in the constructor, and per index search FaceSettingsPreferenceController.getUserId() returns mUserId set by setUserId(); the ordering is not shown.
- Which Settings screens construct the four controllers, and when.