From ab9088c0ab1f90d5b0eb6055112c5b629c26a228 Mon Sep 17 00:00:00 2001 From: Muntashir Al-Islam Date: Sat, 14 Sep 2024 06:16:36 -0700 Subject: [PATCH] [Refactor] Use the new biometric API Signed-off-by: Muntashir Al-Islam --- app/build.gradle | 1 + .../AppManager/BaseActivity.java | 43 +++++++--- .../compat/BiometricAuthenticatorsCompat.java | 79 +++++++++++++++++++ .../AppManager/main/SplashActivity.java | 41 +++++++--- versions.gradle | 1 + 5 files changed, 141 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/io/github/muntashirakon/AppManager/compat/BiometricAuthenticatorsCompat.java diff --git a/app/build.gradle b/app/build.gradle index d57fa32273a..a22fc929152 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -167,6 +167,7 @@ dependencies { implementation "androidx.documentfile:documentfile:${documentfile_version}" implementation "androidx.activity:activity:${activity_version}" implementation "androidx.core:core-splashscreen:${splashscreen_version}" + implementation "androidx.biometric:biometric:${biometric_version}" implementation "androidx.webkit:webkit:${webkit_version}" implementation "io.github.Rosemoe.sora-editor:editor:${sora_editor_version}" implementation "io.github.Rosemoe.sora-editor:language-textmate:${sora_editor_version}" diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/BaseActivity.java b/app/src/main/java/io/github/muntashirakon/AppManager/BaseActivity.java index b98e42457d9..076ea561ea6 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/BaseActivity.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/BaseActivity.java @@ -15,10 +15,14 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; import androidx.annotation.IdRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.menu.MenuBuilder; +import androidx.biometric.BiometricPrompt; +import androidx.biometric.BiometricPrompt.AuthenticationResult; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; @@ -27,6 +31,8 @@ import java.util.HashMap; import java.util.List; +import io.github.muntashirakon.AppManager.compat.BiometricAuthenticatorsCompat; +import io.github.muntashirakon.AppManager.crypto.auth.AuthManager; import io.github.muntashirakon.AppManager.crypto.ks.KeyStoreActivity; import io.github.muntashirakon.AppManager.crypto.ks.KeyStoreManager; import io.github.muntashirakon.AppManager.logs.Log; @@ -53,22 +59,13 @@ public abstract class BaseActivity extends AppCompatActivity { @Nullable private SecurityAndOpsViewModel mViewModel; private boolean mDisplayLoader = true; + private BiometricPrompt mBiometricPrompt; private final ActivityResultLauncher mKeyStoreActivity = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { // Need authentication and/or verify mode of operation ensureSecurityAndModeOfOp(); }); - private final ActivityResultLauncher mAuthActivity = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == RESULT_OK) { - // Success - handleMigrationAndModeOfOp(); - } else { - // Authentication failed - finishAndRemoveTask(); - } - }); private final ActivityResultLauncher mPermissionCheckActivity = registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), permissionStatusMap -> { @@ -91,6 +88,25 @@ protected final void onCreate(@Nullable Bundle savedInstanceState) { } // Run authentication mViewModel = new ViewModelProvider(this).get(SecurityAndOpsViewModel.class); + mBiometricPrompt = new BiometricPrompt(this, ContextCompat.getMainExecutor(this), + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + finishAndRemoveTask(); + } + + @Override + public void onAuthenticationSucceeded(@NonNull AuthenticationResult result) { + super.onAuthenticationSucceeded(result); + handleMigrationAndModeOfOp(); + } + + @Override + public void onAuthenticationFailed() { + super.onAuthenticationFailed(); + } + }); mAlertDialog = UIUtils.getProgressDialog(this, getString(R.string.initializing), true); Log.d(TAG, "Waiting to be authenticated."); mViewModel.authenticationStatus().observe(this, status -> { @@ -219,8 +235,11 @@ private void ensureSecurityAndModeOfOp() { KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); if (keyguardManager.isKeyguardSecure()) { // Screen lock enabled - Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.unlock_app_manager), null); - mAuthActivity.launch(intent); + BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle(getString(R.string.unlock_app_manager)) + .setAllowedAuthenticators(new BiometricAuthenticatorsCompat.Builder().allowEverything(true).build()) + .build(); + mBiometricPrompt.authenticate(promptInfo); } else { // Screen lock disabled UIUtils.displayLongToast(R.string.screen_lock_not_enabled); diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/compat/BiometricAuthenticatorsCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/compat/BiometricAuthenticatorsCompat.java new file mode 100644 index 00000000000..03f750fa23e --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/AppManager/compat/BiometricAuthenticatorsCompat.java @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package io.github.muntashirakon.AppManager.compat; + +import android.os.Build; + +import androidx.biometric.BiometricManager.Authenticators; + +public class BiometricAuthenticatorsCompat { + public static final class Builder { + private boolean mAllowWeak = false; + private boolean mAllowStrong = false; + private boolean mAllowDeviceCredential = false; + private boolean mDeviceCredentialOnly = false; + + public Builder() { + } + + public Builder allowEverything(boolean allow) { + mAllowWeak = allow; + mAllowDeviceCredential = allow; + return this; + } + + public Builder allowWeakBiometric(boolean allow) { + mAllowWeak = allow; + return this; + } + + public Builder allowStrongBiometric(boolean allow) { + mAllowStrong = allow; + return this; + } + + public Builder allowDeviceCredential(boolean allow) { + mAllowDeviceCredential = allow; + return this; + } + + public Builder deviceCredentialOnly(boolean only) { + mDeviceCredentialOnly = only; + return this; + } + + public int build() { + if (mDeviceCredentialOnly) { + return getDeviceCredentialOnlyFlags(); + } + int flags; + if (mAllowWeak) { + flags = Authenticators.BIOMETRIC_WEAK; + } else if (mAllowStrong) { + flags = Authenticators.BIOMETRIC_STRONG; + } else flags = 0; + if (mAllowDeviceCredential) { + if (flags == 0) { + return getDeviceCredentialOnlyFlags(); + } + if (flags == Authenticators.BIOMETRIC_STRONG && ( + Build.VERSION.SDK_INT < Build.VERSION_CODES.P + || Build.VERSION.SDK_INT > Build.VERSION_CODES.Q)) { + flags = Authenticators.BIOMETRIC_WEAK; + } + return flags | Authenticators.DEVICE_CREDENTIAL; + } + return flags; + } + + private int getDeviceCredentialOnlyFlags() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Authenticators.DEVICE_CREDENTIAL; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + return Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL; + } + return Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL; + } + } +} diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/main/SplashActivity.java b/app/src/main/java/io/github/muntashirakon/AppManager/main/SplashActivity.java index 581f8c12964..48893fb0a59 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/main/SplashActivity.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/main/SplashActivity.java @@ -14,9 +14,12 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.menu.MenuBuilder; +import androidx.biometric.BiometricPrompt; +import androidx.core.content.ContextCompat; import androidx.core.splashscreen.SplashScreen; import androidx.lifecycle.ViewModelProvider; @@ -26,6 +29,7 @@ import io.github.muntashirakon.AppManager.BuildConfig; import io.github.muntashirakon.AppManager.R; +import io.github.muntashirakon.AppManager.compat.BiometricAuthenticatorsCompat; import io.github.muntashirakon.AppManager.crypto.ks.KeyStoreActivity; import io.github.muntashirakon.AppManager.crypto.ks.KeyStoreManager; import io.github.muntashirakon.AppManager.logs.Log; @@ -42,22 +46,13 @@ public class SplashActivity extends AppCompatActivity { @Nullable private TextView mStateNameView; private SecurityAndOpsViewModel mViewModel; + private BiometricPrompt mBiometricPrompt; private final ActivityResultLauncher mKeyStoreActivity = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { // Need authentication and/or verify mode of operation ensureSecurityAndModeOfOp(); }); - private final ActivityResultLauncher mAuthActivity = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == RESULT_OK) { - // Success - handleMigrationAndModeOfOp(); - } else { - // Authentication failed - finishAndRemoveTask(); - } - }); @Override protected final void onCreate(@Nullable Bundle savedInstanceState) { @@ -83,6 +78,25 @@ protected final void onCreate(@Nullable Bundle savedInstanceState) { } // Run authentication mViewModel = new ViewModelProvider(this).get(SecurityAndOpsViewModel.class); + mBiometricPrompt = new BiometricPrompt(this, ContextCompat.getMainExecutor(this), + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + finishAndRemoveTask(); + } + + @Override + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + super.onAuthenticationSucceeded(result); + handleMigrationAndModeOfOp(); + } + + @Override + public void onAuthenticationFailed() { + super.onAuthenticationFailed(); + } + }); Log.d(TAG, "Waiting to be authenticated."); mViewModel.authenticationStatus().observe(this, status -> { switch (status) { @@ -158,8 +172,11 @@ private void ensureSecurityAndModeOfOp() { KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); if (keyguardManager.isKeyguardSecure()) { // Screen lock enabled - Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.unlock_app_manager), null); - mAuthActivity.launch(intent); + BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle(getString(R.string.unlock_app_manager)) + .setAllowedAuthenticators(new BiometricAuthenticatorsCompat.Builder().allowEverything(true).build()) + .build(); + mBiometricPrompt.authenticate(promptInfo); } else { // Screen lock disabled UIUtils.displayLongToast(R.string.screen_lock_not_enabled); diff --git a/versions.gradle b/versions.gradle index 15c2394f5d1..ed06a390b5c 100644 --- a/versions.gradle +++ b/versions.gradle @@ -16,6 +16,7 @@ ext { appcompat_version = "1.7.0" arsclib_version = "ece5c8a43c" baksmali_version = "3.0.7" + biometric_version = "1.2.0-alpha05" bouncycastle_version = "1.78.1" desugar_jdk_version = "2.0.4" documentfile_version = "1.1.0-alpha01" // AppCompat still includes the buggy implementation of documentfile library (1.0.0)