Skip to content

Commit 50788d1

Browse files
authored
Merge pull request #119 from android/single_tap_sign_in
Biometric(Single tap credential creation & sign-in) implementation in MyVault Provider
2 parents 5829293 + ea78f1e commit 50788d1

18 files changed

+819
-241
lines changed

CredentialProvider/MyVault/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The app demonstrates how to:
1414
- Register as a `CredentialProviderService` so that users can store and retrieve passwords and passkeys using the app.
1515
- Save passwords/passkeys to the app. These are stored locally in a database for demonstration purposes only. In a real app this data should be sent to a server to allow the user's credentials to be synchronized across all their devices.
1616
- Retrieve credentials from the app to assist with user login in another app or website.
17+
- Implement your own biometric prompts for single-tap credential creation and sign-in.
1718
- Delete passkeys or passwords.
1819

1920
# Requirements
@@ -104,6 +105,9 @@ These additional activities are described below.
104105

105106
For more detailed information on how to create, save and retrieve credentials using the Credential Manager API, refer to the [official documentation]((https://developer.android.com/training/sign-in/credential-provider))
106107

108+
To implement your own biometrics prompt, refer to the [biometrics documentation](https://developer.android.com/identity/sign-in/single-tap-biometric)
109+
110+
107111
## License
108112

109113
The **MyVault Sample** is distributed under the terms of the Apache License (Version 2.0).

CredentialProvider/MyVault/app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ plugins {
2222
android {
2323
namespace = "com.example.android.authentication.myvault"
2424

25-
compileSdkPreview = "VanillaIceCream"
25+
compileSdk = 35
2626

2727
defaultConfig {
2828
applicationId = "com.example.android.authentication.myvault"
2929
minSdk = 34
30-
targetSdkPreview = "VanillaIceCream"
30+
targetSdk= 35
3131
versionCode = 1
3232
versionName = "1.0"
3333

CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/AppDependencies.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ object AppDependencies {
4040

4141
var providerIcon: Icon? = null
4242

43-
lateinit var RPIconDataSource: RPIconDataSource
43+
lateinit var rpIconDataSource: RPIconDataSource
4444

4545
/**
4646
* Initializes the core components required for the application's data storage and icon handling.
@@ -63,7 +63,7 @@ object AppDependencies {
6363
.fallbackToDestructiveMigration()
6464
.build()
6565

66-
RPIconDataSource = RPIconDataSource(context.applicationInfo.dataDir)
66+
rpIconDataSource = RPIconDataSource(context.applicationInfo.dataDir)
6767
providerIcon = Icon.createWithResource(context, R.drawable.android_secure)
6868

6969
credentialsRepository =
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.android.authentication.myvault
17+
18+
import android.annotation.SuppressLint
19+
import android.content.Context
20+
import androidx.credentials.provider.BiometricPromptResult
21+
22+
/**
23+
* Utility class for handling biometric authentication errors.
24+
*
25+
* <p>This class provides a collection of static utility methods for managing
26+
* and processing errors that may occur during biometric authentication flows.
27+
* It encapsulates the logic for extracting error information from
28+
* {@link BiometricPromptResult} objects and constructing user-friendly error
29+
* messages.
30+
*
31+
* <p>The primary function of this class is to centralize the error-handling
32+
* logic related to biometric authentication, promoting code reuse and
33+
* maintainability.
34+
*/
35+
object BiometricErrorUtils {
36+
37+
/**
38+
* Checks if there was an error during the biometric authentication flow and returns an error message if so.
39+
*
40+
* <p>This method determines whether the biometric authentication flow resulted in
41+
* an error. It checks if the {@link BiometricPromptResult} is null or if the
42+
* authentication was successful. If neither of these conditions is met, it
43+
* extracts the error code and message from the {@link BiometricPromptResult},
44+
* constructs an error message, and returns it.
45+
*
46+
* <p>The error message is built using the following format:
47+
* "Biometric Error Code: [errorCode] [errorMessage] Other providers may be available."
48+
*
49+
* @param context The context used to retrieve string resources.
50+
* @param biometricPromptResult The result of the biometric authentication prompt.
51+
* @return An error message if there was an error during the biometric flow, or an empty string otherwise.
52+
*/
53+
@SuppressLint("StringFormatMatches")
54+
fun getBiometricErrorMessage(
55+
context: Context,
56+
biometricPromptResult: BiometricPromptResult?,
57+
): String {
58+
// If the biometricPromptResult is null, there was no error.
59+
if (biometricPromptResult == null) return context.getString(R.string.empty)
60+
61+
// If the biometricPromptResult indicates success, there was no error.
62+
if (biometricPromptResult.isSuccessful) return context.getString(R.string.empty)
63+
64+
// Initialize default values for the error code and message.
65+
var biometricAuthErrorCode = -1
66+
var biometricAuthErrorMsg = context.getString(R.string.unknown_failure)
67+
68+
// Check if there is an authentication error in the biometricPromptResult.
69+
if (biometricPromptResult.authenticationError != null) {
70+
// Extract the error code and message from the authentication error.
71+
biometricAuthErrorCode = biometricPromptResult.authenticationError!!.errorCode
72+
biometricAuthErrorMsg = biometricPromptResult.authenticationError!!.errorMsg.toString()
73+
}
74+
75+
// Build the error message to be sent to the client.
76+
val errorMessage = buildString {
77+
append(
78+
context.getString(
79+
R.string.biometric_error_code_with_message,
80+
biometricAuthErrorCode,
81+
),
82+
)
83+
append(biometricAuthErrorMsg)
84+
append(context.getString(R.string.other_providers_error_message))
85+
}
86+
87+
// Indicate that there was an error during the biometric flow.
88+
return errorMessage
89+
}
90+
}

0 commit comments

Comments
 (0)