├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── eu
│ │ └── rafareborn
│ │ └── biometricbypass
│ │ ├── BiometricBypassModule.kt
│ │ └── hooker
│ │ └── BiometricBypassHooker.kt
│ ├── res
│ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_background.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_background.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_background.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_background.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_background.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ └── values
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
│ └── resources
│ └── META-INF
│ └── xposed
│ ├── java_init.list
│ ├── module.prop
│ └── scope.list
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── changelogs
│ ├── 100.txt
│ └── 101.txt
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ └── 01_module_enabled.png
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── media
├── module_disabled.gif
└── module_enabled.gif
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Android Studio
6 | .idea/
7 | *.iml
8 |
9 | # Local configuration file (SDK path, etc.)
10 | local.properties
11 |
12 | # Log/OS Files
13 | .DS_Store
14 | *.log
15 |
16 | # NDK/CMake
17 | .cxx/
18 | .externalNativeBuild/
19 |
20 | # Output and generated files
21 | captures/
22 | outputs/
23 |
24 | # Unit test files
25 | test-output/
26 |
27 | # Android Studio specific
28 | .idea/
29 | *.iws
30 | *.ipr
31 | *.iml
32 |
33 | # Android Studio 3.1+ specific
34 | .idea/caches/
35 | .idea/modules.xml
36 | .idea/workspace.xml
37 | .idea/codeStyles/
38 | .idea/libraries/
39 | .idea/shelf/
40 | .idea/gradle.xml
41 | .idea/sonarlint/
42 | .idea/vcs.xml
43 | .idea/runConfigurations.xml
44 | .idea/runConfigurations/
45 | .idea/misc.xml
46 | .idea/navEditor.xml
47 |
48 | # Keystore files
49 | *.jks
50 |
51 | # Firebase Crashlytics
52 | crashlytics.properties
53 |
54 | # Gradle Wrapper
55 | gradle-wrapper.jar
56 | gradle-wrapper.properties
57 |
58 | # Build folders
59 | build/
60 | app/build/
61 | gradle/
62 |
63 | # MacOS specific files
64 | .DS_Store
65 |
66 | # Temporary files
67 | *.tmp
68 | *.temp
69 |
70 | # Exception for libs.versions.toml
71 | !gradle/libs.versions.toml
72 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libxposed/api"]
2 | path = libxposed/api
3 | url = https://github.com/libxposed/api.git
4 | [submodule "libxposed/service"]
5 | path = libxposed/service
6 | url = https://github.com/libxposed/service.git
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Rafael Gale
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Biometric Bypass Module
2 |
3 | **Streamlines face unlock by skipping biometric confirmation in System UI (Android 10+)**
4 |
5 | [](https://app.codacy.com/gh/rafareborn/biometric-bypass)
6 | 
7 | 
8 | 
9 |
10 | ## Overview
11 |
12 | This LSPosed module streamlines face unlock by skipping the confirmation step enforced after biometric authentication. It applies the bypass system-wide — so it works across **all apps**, including banking or security-sensitive ones.
13 |
14 | Android introduced the [`setConfirmationRequired(false)`](https://developer.android.com/identity/sign-in/biometric-auth#no-explicit-user-action) flag in Android 10 to support passive authentication flows (e.g., face unlock without requiring a tap). Since most apps don’t disable confirmation explicitly, Android defaults to requiring a manual tap, turning face unlock into a two-step chore.
15 |
16 | This module ensures the confirmation step is skipped across all biometric flows, regardless of app implementation.
17 |
18 | ---
19 |
20 | ## How it Works
21 |
22 | Android 10 (API 29) added support for passive biometric flows via the `setConfirmationRequired(false)` flag in the BiometricPrompt API. This allows apps to skip the "tap to confirm" step after face unlock — **but only** if:
23 |
24 | - The app explicitly sets `setConfirmationRequired(false)`
25 | - The biometric method is classified as **Class 3 (strong)** (e.g., secure face unlock on Pixel 8+)
26 |
27 | Most apps don’t set this flag, and even when they do, some components still enforce the confirmation dialog.
28 |
29 | This module hooks System UI directly to eliminate that dialog, simulating the intended behavior system-wide, no matter what the app does.
30 |
31 | ---
32 |
33 | ## Visual Comparison
34 |
35 |
36 |
37 |
38 | Default Behavior: Face unlock with manual confirmation required.
39 |
40 |
41 |
42 | Module Enabled: Face unlock with automatic confirmation bypass.
43 |
44 |
45 | ---
46 |
47 | ## Compatibility
48 |
49 | - **Android Versions:** 10 and up (API 29+)
50 | - **ROM Support:** AOSP-based ROMs, Pixel, and other close-to-stock systems
51 | OEM ROMs (e.g. MIUI, OneUI) are **not tested and probably won't work** due to heavy modifications
52 | - **App Support:** Works globally, including banking and security-sensitive apps by applying the bypass system-wide
53 |
54 | ---
55 |
56 | ## Installation
57 |
58 | 1. Install [LSPosed](https://github.com/LSPosed/LSPosed/releases)
59 | 2. Download and install the module APK
60 | 3. In LSPosed, enable the module and apply it to **System UI**
61 | 4. Restart System UI or reboot the device
62 |
63 | ---
64 |
65 | ## Legacy Xposed Support (Archived)
66 |
67 | These branches are unmaintained and only exist for migration or historical reference:
68 |
69 | - [Legacy Xposed - Java](https://github.com/rafareborn/biometric-bypass/tree/legacy-xposed-java)
70 | - [Legacy Xposed - Kotlin](https://github.com/rafareborn/biometric-bypass/tree/legacy-xposed-kotlin)
71 |
72 | ---
73 |
74 | ## Risks
75 |
76 | Bypassing confirmation reduces friction and security. If someone spoofs your face or waves your phone at you while you’re asleep, they get in. Use responsibly.
77 |
78 | ---
79 |
80 | ## Contributing
81 |
82 | Pull requests are welcome. Issues too.
83 |
84 | ---
85 |
86 | ## License
87 |
88 | MIT. Use it, fork it, misconfigure it.
89 | If your cat unlocks your phone and sends crypto to North Korea, that’s on you.
90 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
3 | /debug
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.agp.app)
3 | alias(libs.plugins.kotlin)
4 | }
5 |
6 | android {
7 | namespace = "eu.rafareborn.biometricbypass"
8 | compileSdk = 35
9 |
10 | defaultConfig {
11 | applicationId = "eu.rafareborn.biometricbypass"
12 | minSdk = 29
13 | targetSdk = 35
14 |
15 | versionCode = 101
16 | versionName = "1.0.1"
17 | }
18 |
19 | signingConfigs {
20 | create("release") {
21 | fun secret(name: String): String? =
22 | providers.gradleProperty(name)
23 | .orElse(providers.environmentVariable(name))
24 | .orNull
25 |
26 | val storeFilePath = secret("RELEASE_STORE_FILE")
27 | val storePassword = secret("RELEASE_STORE_PASSWORD")
28 | val keyAlias = secret("RELEASE_KEY_ALIAS")
29 | val keyPassword = secret("RELEASE_KEY_PASSWORD")
30 | val storeType = secret("RELEASE_STORE_TYPE") ?: "PKCS12"
31 |
32 | if (!storeFilePath.isNullOrBlank()) {
33 | storeFile = file(storeFilePath)
34 | this.storePassword = storePassword
35 | this.keyAlias = keyAlias
36 | this.keyPassword = keyPassword
37 | this.storeType = storeType
38 |
39 | enableV1Signing = false
40 | enableV2Signing = true
41 | } else {
42 | logger.warn("RELEASE_STORE_FILE not found. Release signing is disabled.")
43 | }
44 | }
45 | }
46 |
47 | buildTypes {
48 | release {
49 | isMinifyEnabled = true
50 | isShrinkResources = true
51 | proguardFiles(
52 | getDefaultProguardFile("proguard-android-optimize.txt"),
53 | "proguard-rules.pro"
54 | )
55 | signingConfig = signingConfigs.getByName("release")
56 | }
57 | debug {
58 | isMinifyEnabled = false
59 | isShrinkResources = false
60 | }
61 | }
62 |
63 | dependenciesInfo {
64 | includeInApk = false
65 | includeInBundle = false
66 | }
67 |
68 | buildFeatures {
69 | viewBinding = true
70 | buildConfig = true
71 | }
72 |
73 | kotlin {
74 | jvmToolchain(21)
75 | }
76 |
77 | compileOptions {
78 | sourceCompatibility = JavaVersion.VERSION_21
79 | targetCompatibility = JavaVersion.VERSION_21
80 | }
81 |
82 | packaging {
83 | resources {
84 | merges += "META-INF/xposed/*"
85 | excludes += setOf(
86 | "META-INF/LICENSE",
87 | "META-INF/LICENSE.txt",
88 | "META-INF/NOTICE",
89 | "META-INF/NOTICE.txt",
90 | "META-INF/AL2.0",
91 | "META-INF/LGPL2.1",
92 | "META-INF/*.kotlin_module",
93 | "META-INF/INDEX.LIST"
94 | )
95 | }
96 | }
97 |
98 | lint {
99 | abortOnError = true
100 | disable.add("OldTargetApi")
101 | }
102 | }
103 |
104 | dependencies {
105 | implementation(libs.libxposed.service)
106 | compileOnly(libs.libxposed.api)
107 | implementation(libs.kotlinx.coroutines)
108 | }
109 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Xposed
2 | -adaptresourcefilecontents META-INF/xposed/java_init.list
3 | -keepattributes RuntimeVisibleAnnotations
4 | -keep,allowobfuscation,allowoptimization public class * extends io.github.libxposed.api.XposedModule {
5 | public (...);
6 | public void onPackageLoaded(...);
7 | public void onSystemServerLoaded(...);
8 | }
9 | -keep,allowoptimization,allowobfuscation @io.github.libxposed.api.annotations.* class * {
10 | ;
11 | ;
12 | }
13 |
14 | # Reflection-Based Keep Rules
15 | -keepclassmembers class * {
16 | @io.github.libxposed.api.annotations.* *;
17 | }
18 |
19 | # Kotlin
20 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
21 | public static void check*(...);
22 | public static void throw*(...);
23 | }
24 | -assumenosideeffects class java.util.Objects {
25 | public static ** requireNonNull(...);
26 | }
27 |
28 | # Strip debug log
29 | -assumenosideeffects class android.util.Log {
30 | public static int v(...);
31 | public static int d(...);
32 | }
33 |
34 | # Obfuscation
35 | -repackageclasses
36 | -allowaccessmodification
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rafareborn/biometric-bypass/a4b201e3a681e67e731773f108fab9a270f79302/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/eu/rafareborn/biometricbypass/BiometricBypassModule.kt:
--------------------------------------------------------------------------------
1 | package eu.rafareborn.biometricbypass
2 |
3 | import android.annotation.SuppressLint
4 | import eu.rafareborn.biometricbypass.hooker.BiometricBypassHooker
5 | import io.github.libxposed.api.XposedInterface
6 | import io.github.libxposed.api.XposedModule
7 | import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
8 | import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
9 |
10 | internal lateinit var module: BiometricBypassModule
11 |
12 | class BiometricBypassModule(base: XposedInterface, param: ModuleLoadedParam) : XposedModule(base, param) {
13 |
14 | init {
15 | module = this
16 | }
17 |
18 | @SuppressLint("PrivateApi")
19 | override fun onPackageLoaded(param: PackageLoadedParam) {
20 | if (param.packageName != TARGET_PACKAGE || !param.isFirstPackage) return
21 |
22 | module.log("$TAG Loaded package: ${param.packageName}")
23 |
24 | try {
25 | hookTargetMethod(param.classLoader)
26 | } catch (e: ReflectiveOperationException) {
27 | module.log("$TAG Error: ${e::class.simpleName} - ${e.message}")
28 | } catch (e: Exception) {
29 | module.log("$TAG Unexpected error: ${e.message}")
30 | }
31 | }
32 |
33 | @SuppressLint("PrivateApi")
34 | private fun hookTargetMethod(classLoader: ClassLoader) {
35 | val targetClass = classLoader.loadClass(TARGET_CLASS)
36 | val targetMethod = targetClass.getDeclaredMethod(TARGET_METHOD)
37 | module.hook(targetMethod, BiometricBypassHooker::class.java)
38 | module.log("$TAG Successfully hooked method: $TARGET_METHOD in $TARGET_CLASS")
39 | }
40 |
41 | companion object {
42 | const val TAG = "BiometricBypassModule"
43 | private const val TARGET_PACKAGE = "com.android.systemui"
44 | private const val TARGET_CLASS = "com.android.systemui.biometrics.AuthContainerView"
45 | private const val TARGET_METHOD = "onDialogAnimatedIn"
46 | const val BUTTON_CONFIRM_ID = "button_confirm"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/rafareborn/biometricbypass/hooker/BiometricBypassHooker.kt:
--------------------------------------------------------------------------------
1 | package eu.rafareborn.biometricbypass.hooker
2 |
3 | import android.annotation.SuppressLint
4 | import android.view.View
5 | import android.widget.Button
6 | import eu.rafareborn.biometricbypass.BiometricBypassModule
7 | import eu.rafareborn.biometricbypass.BiometricBypassModule.Companion.TAG
8 | import eu.rafareborn.biometricbypass.module
9 | import io.github.libxposed.api.XposedInterface
10 | import io.github.libxposed.api.annotations.AfterInvocation
11 | import io.github.libxposed.api.annotations.XposedHooker
12 | import kotlinx.coroutines.CoroutineScope
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.delay
15 | import kotlinx.coroutines.launch
16 |
17 | @XposedHooker
18 | class BiometricBypassHooker : XposedInterface.Hooker {
19 |
20 | companion object {
21 | private const val MAX_RETRIES = 3
22 | private const val INITIAL_DELAY_MS = 100L
23 |
24 | @JvmStatic
25 | @AfterInvocation
26 | fun afterInvocation(callback: XposedInterface.AfterHookCallback) {
27 | val authContainerView = callback.thisObject as? View ?: return
28 | val context = authContainerView.context ?: return
29 |
30 | @SuppressLint("DiscouragedApi")
31 | val confirmButtonId = context.resources.getIdentifier(
32 | BiometricBypassModule.BUTTON_CONFIRM_ID,
33 | "id",
34 | context.packageName
35 | )
36 |
37 | CoroutineScope(Dispatchers.Main).launch {
38 | retryClickButton(authContainerView, confirmButtonId)
39 | }
40 | }
41 |
42 | private suspend fun retryClickButton(parentView: View, buttonId: Int) {
43 | var delayTime = INITIAL_DELAY_MS
44 |
45 | repeat(MAX_RETRIES) { attempt ->
46 | parentView.findViewById