Skip to content

Commit

Permalink
[Refactor] Use the new biometric API
Browse files Browse the repository at this point in the history
Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
  • Loading branch information
MuntashirAkon committed Sep 14, 2024
1 parent 1c9ce7d commit ab9088c
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 24 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -53,22 +59,13 @@ public abstract class BaseActivity extends AppCompatActivity {
@Nullable
private SecurityAndOpsViewModel mViewModel;
private boolean mDisplayLoader = true;
private BiometricPrompt mBiometricPrompt;

private final ActivityResultLauncher<Intent> mKeyStoreActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
// Need authentication and/or verify mode of operation
ensureSecurityAndModeOfOp();
});
private final ActivityResultLauncher<Intent> mAuthActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
// Success
handleMigrationAndModeOfOp();
} else {
// Authentication failed
finishAndRemoveTask();
}
});
private final ActivityResultLauncher<String[]> mPermissionCheckActivity = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
permissionStatusMap -> {
Expand All @@ -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 -> {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -42,22 +46,13 @@ public class SplashActivity extends AppCompatActivity {
@Nullable
private TextView mStateNameView;
private SecurityAndOpsViewModel mViewModel;
private BiometricPrompt mBiometricPrompt;

private final ActivityResultLauncher<Intent> mKeyStoreActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
// Need authentication and/or verify mode of operation
ensureSecurityAndModeOfOp();
});
private final ActivityResultLauncher<Intent> 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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ab9088c

Please sign in to comment.