Skip to content

Commit

Permalink
Implement biometric authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
AsemLab committed Sep 30, 2024
1 parent 2de94ff commit 46329b0
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 2 deletions.
13 changes: 13 additions & 0 deletions .settings/org.eclipse.buildship.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
arguments=--init-script C\:\\Users\\asemx\\AppData\\Roaming\\TabNine\\language-servers\\bundled_java\\1.24.0\\app\\configuration\\org.eclipse.osgi\\53\\0\\.cp\\gradle\\init\\init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=C\:/Program Files/Java/jre17
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,5 @@ dependencies {
implementation(libs.epoxy)
implementation(libs.epoxy.databinding)
kapt(libs.epoxy.processor)
implementation(libs.androidx.biometric)
}
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.ContactsContent"
tools:targetApi="31">
<activity
android:name=".ui.BiometricActivity"
android:exported="true" />
<activity
android:name=".passdata.f2f.ThirdActivity"
android:exported="true">
Expand Down Expand Up @@ -61,7 +64,6 @@
<!-- </intent-filter>-->
</activity>
<activity android:name=".RegisterForResultActivity" />

<activity
android:name=".NotificationProgressActivity"
android:exported="true">
Expand All @@ -80,7 +82,6 @@
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>

</application>

</manifest>
100 changes: 100 additions & 0 deletions app/src/main/java/com/asemlab/samples/ui/BiometricActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.asemlab.samples.ui

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.DataBindingUtil
import com.asemlab.samples.R
import com.asemlab.samples.databinding.ActivityBiometricBinding
import com.asemlab.samples.utils.BiometricUtils

// TODO Show a biometric authentication dialog using the Biometric library
class BiometricActivity : AppCompatActivity() {

private lateinit var binding: ActivityBiometricBinding
private val activityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it?.resultCode == RESULT_OK) {
Toast.makeText(this, "OK", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "CANCELED", Toast.LENGTH_SHORT).show()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = DataBindingUtil.setContentView(this, R.layout.activity_biometric)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}

with(BiometricUtils) {
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Toast.makeText(
this@BiometricActivity,
"Biometric error: $errorCode, $errString",
Toast.LENGTH_SHORT
).show()
}

override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Toast.makeText(this@BiometricActivity, "Biometric failed!", Toast.LENGTH_SHORT)
.show()
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Toast.makeText(
this@BiometricActivity,
"Biometric Succeeded!",
Toast.LENGTH_SHORT
).show()
}
}
initBiometricManager(this@BiometricActivity, callback)
buildPromptInfo("Login to BiometricApp", "Log in using your fingerprint or face")
}

with(binding) {
loginButton.setOnClickListener {
BiometricUtils.canAuthenticate(onSuccess = {
BiometricUtils.authenticate()
}, onError = { msg ->
Toast.makeText(
this@BiometricActivity,
"Biometric error: $msg",
Toast.LENGTH_SHORT
).show()
}) {
// TODO If the user doesn't enable the biometric authentication, open sittings if supported (optional)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val enrollIntent =
Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
)
}
activityResult.launch(enrollIntent)
}
}
}
}
}
}
78 changes: 78 additions & 0 deletions app/src/main/java/com/asemlab/samples/utils/BiometricUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.asemlab.samples.utils

import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import java.util.concurrent.Executor

// TODO Must add the Biometric dependency
object BiometricUtils {

private lateinit var biometricManager: BiometricManager
private lateinit var executor: Executor
private lateinit var biometricPrompt: BiometricPrompt
private lateinit var promptInfo: BiometricPrompt.PromptInfo

fun initBiometricManager(
context: AppCompatActivity,
callback: BiometricPrompt.AuthenticationCallback
) {
// TODO 1. Init the biometric manager
biometricManager = BiometricManager.from(context)
executor = ContextCompat.getMainExecutor(context)
biometricPrompt = BiometricPrompt(context, executor, callback)

}

fun buildPromptInfo(
title: String = "",
description: String = "",
negationText: String = "Cancel"
) {
// TODO 2. Build the PromptInfo, used for set info for the dialog
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(title)
.setSubtitle(description)
.setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
// .setNegativeButtonText("negationText") // Apply when DEVICE_CREDENTIAL isn't enabled
.build()
}

fun canAuthenticate(
onSuccess: () -> Unit,
onError: (String) -> Unit,
onNotEnabled: () -> Unit
) {
// TODO Check if biometric authentication is supported on the device (optional)
when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
Log.d("BiometricUtils", "App can authenticate using biometrics.")
onSuccess()
}

BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
Log.e("BiometricUtils", "No biometric features available on this device.")
onError("No biometric features available on this device.")
}

BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
Log.e("BiometricUtils", "Biometric features are currently unavailable.")
onError("Biometric features are currently unavailable.")
}

BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
Log.e("BiometricUtils", "Biometric not enrolled")
onNotEnabled()
}
}
}

// TODO 3. Show the authentication dialog
fun authenticate() {
biometricPrompt.authenticate(promptInfo)
}
}
24 changes: 24 additions & 0 deletions app/src/main/res/layout/activity_biometric.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.BiometricActivity">

<Button
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="Login with biometric"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ androidx-junit = "1.1.4"
# TODO Next two versions must be > 1.6.0
appcompat = "1.6.1"
androidx-activity = "1.7.2"
biometric = "1.1.0"
constraintlayout = "2.1.4"
core-ktx = "1.7.0"
detektFormatting = "1.23.3"
Expand Down Expand Up @@ -58,6 +59,7 @@ firebase-distribution = "16.0.0-beta13"
[libraries]
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" }
Expand Down

0 comments on commit 46329b0

Please sign in to comment.