From adbd34d4b8424580a4ee18f50cefe40b25c4eb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Thu, 6 Mar 2025 17:00:06 -0300 Subject: [PATCH] Biometric authentication --- app/build.gradle | 8 +- app/src/main/AndroidManifest.xml | 1 + .../com/example/dcav2gui/MainActivity.java | 116 +++++++++++++++++- .../dcav2gui/ui/home/HomeFragment.java | 4 +- gradle/libs.versions.toml | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 6 files changed, 131 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 22edf18..ed686fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 72c85a7..796100d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + 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()); @@ -83,7 +113,7 @@ public class MainActivity extends AppCompatActivity { R.id.nav_gateio, R.id.nav_kucoin, R.id.nav_okx - ) + ) .setOpenableLayout(drawer) .build(); NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main); @@ -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; } diff --git a/app/src/main/java/com/example/dcav2gui/ui/home/HomeFragment.java b/app/src/main/java/com/example/dcav2gui/ui/home/HomeFragment.java index 31267a7..5e8ff42 100644 --- a/app/src/main/java/com/example/dcav2gui/ui/home/HomeFragment.java +++ b/app/src/main/java/com/example/dcav2gui/ui/home/HomeFragment.java @@ -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); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b7b1b64..a9d6b26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9da0c5..1cd5105 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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