Biometric authentication

This commit is contained in:
Nicolás Sánchez 2025-03-06 17:00:06 -03:00
parent ad5929a774
commit adbd34d4b8
6 changed files with 131 additions and 8 deletions

View File

@ -4,12 +4,12 @@ plugins {
android {
namespace 'com.example.dcav2gui'
compileSdk 34
compileSdk 35
defaultConfig {
applicationId "com.example.dcav2gui"
minSdk 24
targetSdk 34
targetSdk 35
versionCode 1
versionName "1.0"
@ -42,7 +42,9 @@ dependencies {
implementation libs.navigation.ui
implementation libs.gson
implementation libs.recyclerview
//implementation libs.okhttp
implementation libs.biometric
implementation libs.activity
implementation libs.fragment
implementation libs.okhttp.v492
implementation libs.firebase.crashlytics.buildtools
testImplementation libs.junit

View File

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -1,7 +1,17 @@
package com.example.dcav2gui;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Build;
import android.provider.Settings;
import android.view.MenuItem;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;//
@ -23,6 +33,7 @@ import com.example.dcav2gui.databinding.ActivityMainBinding;
import java.io.File;
import java.util.Objects;
import java.util.concurrent.Executor;
public class MainActivity extends AppCompatActivity {
@ -35,7 +46,11 @@ public class MainActivity extends AppCompatActivity {
public static InstanceInterface.ExchangeStatsData kucoinCache;
public static InstanceInterface.ExchangeStatsData okxCache;
private BiometricPrompt biometricPrompt;
private BiometricPrompt.PromptInfo promptInfo;
private boolean isAuthenticated = false;
private ActivityResultLauncher<Intent> enrollBiometricLauncher;
public HomeFragment.HomeCache getHomeViewCache() {
return homeViewCache;
@ -70,7 +85,22 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerBiometricEnrollLauncher();
setupBiometricAuthentication();
}
private void registerBiometricEnrollLauncher() {
enrollBiometricLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
// After returning from enrollment screen, try authentication again
// or initialize the app if enrollment was canceled
setupBiometricAuthentication();
}
);
}
private void initializeApp() {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -99,6 +129,90 @@ public class MainActivity extends AppCompatActivity {
}
}
private void setupBiometricAuthentication() {
BiometricManager biometricManager = BiometricManager.from(this);
switch (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
case BiometricManager.BIOMETRIC_SUCCESS:
// Device supports biometric authentication
setupBiometricPrompt();
break;
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
// No biometric features available on this device
Toast.makeText(this, "Fingerprint authentication not available", Toast.LENGTH_LONG).show();
initializeApp(); // Skip to app initialization
break;
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
// Biometric features are currently unavailable
Toast.makeText(this, "Biometric authentication temporarily unavailable", Toast.LENGTH_LONG).show();
initializeApp(); // Skip to app initialization
break;
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
// No biometrics or PIN enrolled, prompt user to set them up
Toast.makeText(this, "No fingerprint or PIN set up on device", Toast.LENGTH_LONG).show();
// For API 30 and above, direct user to enroll biometrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
final Intent enrollIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL);
enrollIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL);
try {
enrollBiometricLauncher.launch(enrollIntent);
} catch (ActivityNotFoundException e) {
initializeApp(); // Skip to app initialization if enrollment activity not found
}
} else {
initializeApp(); // Skip to app initialization for older Android versions
}
break;
default:
initializeApp(); // Skip to app initialization for any other case
}
}
private void setupBiometricPrompt() {
Executor executor = ContextCompat.getMainExecutor(this);
biometricPrompt = new BiometricPrompt(this, executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Toast.makeText(getApplicationContext(), "Authentication error: " + errString, Toast.LENGTH_SHORT).show();
// If authentication is canceled or fails multiple times, exit the app
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON ||
errorCode == BiometricPrompt.ERROR_USER_CANCELED) {
finish();
} else {
// For other errors, try again
biometricPrompt.authenticate(promptInfo);
}
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Toast.makeText(getApplicationContext(), "Authentication succeeded!", Toast.LENGTH_SHORT).show();
isAuthenticated = true;
initializeApp();
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(getApplicationContext(), "Authentication failed", Toast.LENGTH_SHORT).show();
}
});
promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate to access your app")
.setSubtitle("Verify your identity")
.setDescription("Use your fingerprint or PIN to access your trading information")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.build();
// Start authentication
biometricPrompt.authenticate(promptInfo);
}
public static SettingsData getGlobalSettings() {
return globalSettings;
}

View File

@ -1190,9 +1190,9 @@ public class HomeFragment extends Fragment {
public static String timeStampConverter(double timestamp, boolean noDate) {
long linuxTimestamp = (long) timestamp; // Replace with your timestamp
Date date = new Date(linuxTimestamp * 1000);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// Multiply by 1000 to convert to milliseconds
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);// Multiply by 1000 to convert to milliseconds
if (noDate) {
formatter = new SimpleDateFormat("HH:mm:ss");
formatter = new SimpleDateFormat("HH:mm:ss", Locale.ROOT);
}
return formatter.format(date);
}

View File

@ -1,5 +1,8 @@
[versions]
agp = "8.7.3"
activity = "1.10.1"
agp = "8.9.0"
biometric = "1.1.0"
fragment = "1.8.6"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
@ -18,6 +21,9 @@ recyclerview = "1.3.2"
recyclerviewVersion = "1.2.1"
[libraries]
activity = { module = "androidx.activity:activity", version.ref = "activity" }
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
fragment = { module = "androidx.fragment:fragment", version.ref = "fragment" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }

View File

@ -1,6 +1,6 @@
#Mon Dec 09 08:04:51 ART 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists