diff --git a/identity/credentialmanager/build.gradle.kts b/identity/credentialmanager/build.gradle.kts
index fd4d43e1..5ed3c0c5 100644
--- a/identity/credentialmanager/build.gradle.kts
+++ b/identity/credentialmanager/build.gradle.kts
@@ -48,6 +48,11 @@ dependencies {
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
+
+ // [START android_identity_credman_dependency]
+ implementation(libs.androidx.credentials)
+ // [END android_identity_credman_dependency]
+
// [START android_identity_gradle_dependencies]
implementation(libs.androidx.credentials)
@@ -62,4 +67,4 @@ dependencies {
// [END android_identity_siwg_gradle_dependencies]
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
-}
\ No newline at end of file
+}
diff --git a/identity/credentialmanager/src/main/AndroidManifest.xml b/identity/credentialmanager/src/main/AndroidManifest.xml
index 70391952..a2ec1cef 100644
--- a/identity/credentialmanager/src/main/AndroidManifest.xml
+++ b/identity/credentialmanager/src/main/AndroidManifest.xml
@@ -15,7 +15,8 @@
~ limitations under the License.
-->
-
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt
new file mode 100644
index 00000000..04e4fb91
--- /dev/null
+++ b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt
@@ -0,0 +1,491 @@
+package com.example.identity.credentialmanager
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Build.VERSION_CODES
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.annotation.RequiresApi
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricPrompt
+import androidx.biometric.BiometricPrompt.AuthenticationCallback
+import androidx.biometric.BiometricPrompt.AuthenticationResult
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CreatePasswordResponse
+import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CreatePublicKeyCredentialResponse
+import androidx.credentials.GetCredentialResponse
+import androidx.credentials.GetPasswordOption
+import androidx.credentials.GetPublicKeyCredentialOption
+import androidx.credentials.PasswordCredential
+import androidx.credentials.PublicKeyCredential
+import androidx.credentials.provider.BeginCreateCredentialRequest
+import androidx.credentials.provider.BeginCreateCredentialResponse
+import androidx.credentials.provider.BeginCreatePasswordCredentialRequest
+import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
+import androidx.credentials.provider.CallingAppInfo
+import androidx.credentials.provider.CreateEntry
+import androidx.credentials.provider.PendingIntentHandler
+import androidx.credentials.webauthn.AuthenticatorAssertionResponse
+import androidx.credentials.webauthn.AuthenticatorAttestationResponse
+import androidx.credentials.webauthn.FidoPublicKeyCredential
+import androidx.credentials.webauthn.PublicKeyCredentialCreationOptions
+import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions
+import androidx.fragment.app.FragmentActivity
+import java.math.BigInteger
+import java.security.KeyPair
+import java.security.KeyPairGenerator
+import java.security.MessageDigest
+import java.security.SecureRandom
+import java.security.Signature
+import java.security.interfaces.ECPrivateKey
+import java.security.spec.ECGenParameterSpec
+import java.security.spec.ECParameterSpec
+import java.security.spec.ECPoint
+import java.security.spec.EllipticCurve
+
+class CredentialProviderDummyActivity: FragmentActivity() {
+
+ private val PERSONAL_ACCOUNT_ID: String = ""
+ private val FAMILY_ACCOUNT_ID: String = ""
+ private val CREATE_PASSWORD_INTENT: String = ""
+
+ @RequiresApi(VERSION_CODES.M)
+ // [START android_identity_credential_provider_handle_passkey]
+ override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
+ super.onCreate(savedInstanceState, persistentState)
+ // ...
+
+ val request =
+ PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
+
+ val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
+ if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
+ val publicKeyRequest: CreatePublicKeyCredentialRequest =
+ request.callingRequest as CreatePublicKeyCredentialRequest
+ createPasskey(
+ publicKeyRequest.requestJson,
+ request.callingAppInfo,
+ publicKeyRequest.clientDataHash,
+ accountId
+ )
+ }
+ }
+
+ @SuppressLint("RestrictedApi")
+ fun createPasskey(
+ requestJson: String,
+ callingAppInfo: CallingAppInfo?,
+ clientDataHash: ByteArray?,
+ accountId: String?
+ ) {
+ val request = PublicKeyCredentialCreationOptions(requestJson)
+
+ val biometricPrompt = BiometricPrompt(
+ this,
+ { }, // Pass in your own executor
+ object : AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ finish()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ finish()
+ }
+
+ @RequiresApi(VERSION_CODES.P)
+ override fun onAuthenticationSucceeded(
+ result: AuthenticationResult
+ ) {
+ super.onAuthenticationSucceeded(result)
+
+ // Generate a credentialId
+ val credentialId = ByteArray(32)
+ SecureRandom().nextBytes(credentialId)
+
+ // Generate a credential key pair
+ val spec = ECGenParameterSpec("secp256r1")
+ val keyPairGen = KeyPairGenerator.getInstance("EC");
+ keyPairGen.initialize(spec)
+ val keyPair = keyPairGen.genKeyPair()
+
+ // Save passkey in your database as per your own implementation
+
+ // Create AuthenticatorAttestationResponse object to pass to
+ // FidoPublicKeyCredential
+
+ val response = AuthenticatorAttestationResponse(
+ requestOptions = request,
+ credentialId = credentialId,
+ credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
+ origin = appInfoToOrigin(callingAppInfo!!),
+ up = true,
+ uv = true,
+ be = true,
+ bs = true,
+ packageName = callingAppInfo.packageName
+ )
+
+ val credential = FidoPublicKeyCredential(
+ rawId = credentialId,
+ response = response,
+ authenticatorAttachment = "", // Add your authenticator attachment
+ )
+ val result = Intent()
+
+ val createPublicKeyCredResponse =
+ CreatePublicKeyCredentialResponse(credential.json())
+
+ // Set the CreateCredentialResponse as the result of the Activity
+ PendingIntentHandler.setCreateCredentialResponse(
+ result,
+ createPublicKeyCredResponse
+ )
+ setResult(RESULT_OK, result)
+ finish()
+ }
+ }
+ )
+
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Use your screen lock")
+ .setSubtitle("Create passkey for ${request.rp.name}")
+ .setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
+ )
+ .build()
+ biometricPrompt.authenticate(promptInfo)
+ }
+
+ @RequiresApi(VERSION_CODES.P)
+ fun appInfoToOrigin(info: CallingAppInfo): String {
+ val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
+ val md = MessageDigest.getInstance("SHA-256");
+ val certHash = md.digest(cert)
+ // This is the format for origin
+ return "android:apk-key-hash:${b64Encode(certHash)}"
+ }
+ // [END android_identity_credential_provider_handle_passkey]
+
+ @RequiresApi(VERSION_CODES.M)
+ // [START android_identity_credential_provider_password_creation]
+ fun processCreateCredentialRequest(
+ request: BeginCreateCredentialRequest
+ ): BeginCreateCredentialResponse? {
+ when (request) {
+ is BeginCreatePublicKeyCredentialRequest -> {
+ // Request is passkey type
+ return handleCreatePasskeyQuery(request)
+ }
+
+ is BeginCreatePasswordCredentialRequest -> {
+ // Request is password type
+ return handleCreatePasswordQuery(request)
+ }
+ }
+ return null
+ }
+
+ @RequiresApi(VERSION_CODES.M)
+ private fun handleCreatePasswordQuery(
+ request: BeginCreatePasswordCredentialRequest
+ ): BeginCreateCredentialResponse {
+ val createEntries: MutableList = mutableListOf()
+
+ // Adding two create entries - one for storing credentials to the 'Personal'
+ // account, and one for storing them to the 'Family' account. These
+ // accounts are local to this sample app only.
+ createEntries.add(
+ CreateEntry(
+ PERSONAL_ACCOUNT_ID,
+ createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
+ )
+ )
+ createEntries.add(
+ CreateEntry(
+ FAMILY_ACCOUNT_ID,
+ createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
+ )
+ )
+
+ return BeginCreateCredentialResponse(createEntries)
+ }
+ // [END android_identity_credential_provider_password_creation]
+
+ @RequiresApi(VERSION_CODES.M)
+ fun handleEntrySelectionForPasswordCreation(
+ mDatabase: MyDatabase
+ ) {
+ // [START android_identity_credential_provider_entry_selection_password_creation]
+ val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
+ val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
+
+ if (createRequest == null) {
+ return
+ }
+
+ val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
+
+ // Fetch the ID and password from the request and save it in your database
+ mDatabase.addNewPassword(
+ PasswordInfo(
+ request.id,
+ request.password,
+ createRequest.callingAppInfo.packageName
+ )
+ )
+
+ //Set the final response back
+ val result = Intent()
+ val response = CreatePasswordResponse()
+ PendingIntentHandler.setCreateCredentialResponse(result, response)
+ setResult(Activity.RESULT_OK, result)
+ finish()
+ // [END android_identity_credential_provider_entry_selection_password_creation]
+ }
+
+ @RequiresApi(VERSION_CODES.P)
+ private fun handleUserSelectionForPasskeys(
+ mDatabase: MyDatabase
+ ) {
+ // [START android_identity_credential_provider_user_pk_selection]
+ val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
+ val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption
+
+ val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
+ val credIdEnc = requestInfo?.getString("credId").orEmpty()
+
+ // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest
+ val passkey = mDatabase.getPasskey(credIdEnc)
+
+ // Decode the credential ID, private key and user ID
+ val credId = b64Decode(credIdEnc)
+ val privateKey = b64Decode(passkey.credPrivateKey)
+ val uid = b64Decode(passkey.uid)
+
+ val origin = appInfoToOrigin(getRequest.callingAppInfo)
+ val packageName = getRequest.callingAppInfo.packageName
+
+ validatePasskey(
+ publicKeyRequest.requestJson,
+ origin,
+ packageName,
+ uid,
+ passkey.username,
+ credId,
+ privateKey
+ )
+ // [END android_identity_credential_provider_user_pk_selection]
+ }
+
+ @SuppressLint("RestrictedApi")
+ @RequiresApi(VERSION_CODES.M)
+ private fun validatePasskey(
+ requestJson: String,
+ origin: String,
+ packageName: String,
+ uid: ByteArray,
+ username: String,
+ credId: ByteArray,
+ privateKeyBytes: ByteArray,
+ ) {
+ // [START android_identity_credential_provider_user_validation_biometric]
+ val request = PublicKeyCredentialRequestOptions(requestJson)
+ val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
+
+ val biometricPrompt = BiometricPrompt(
+ this,
+ { }, // Pass in your own executor
+ object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationError(
+ errorCode: Int, errString: CharSequence
+ ) {
+ super.onAuthenticationError(errorCode, errString)
+ finish()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ finish()
+ }
+
+ override fun onAuthenticationSucceeded(
+ result: BiometricPrompt.AuthenticationResult
+ ) {
+ super.onAuthenticationSucceeded(result)
+ val response = AuthenticatorAssertionResponse(
+ requestOptions = request,
+ credentialId = credId,
+ origin = origin,
+ up = true,
+ uv = true,
+ be = true,
+ bs = true,
+ userHandle = uid,
+ packageName = packageName
+ )
+
+ val sig = Signature.getInstance("SHA256withECDSA");
+ sig.initSign(privateKey)
+ sig.update(response.dataToSign())
+ response.signature = sig.sign()
+
+ val credential = FidoPublicKeyCredential(
+ rawId = credId,
+ response = response,
+ authenticatorAttachment = "", // Add your authenticator attachment
+ )
+ val result = Intent()
+ val passkeyCredential = PublicKeyCredential(credential.json())
+ PendingIntentHandler.setGetCredentialResponse(
+ result, GetCredentialResponse(passkeyCredential)
+ )
+ setResult(RESULT_OK, result)
+ finish()
+ }
+ }
+ )
+
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Use your screen lock")
+ .setSubtitle("Use passkey for ${request.rpId}")
+ .setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
+ )
+ .build()
+ biometricPrompt.authenticate(promptInfo)
+ // [END android_identity_credential_provider_user_validation_biometric]
+ }
+
+ @RequiresApi(VERSION_CODES.M)
+ private fun handleUserSelectionForPasswordAuthentication(
+ mDatabase: MyDatabase,
+ callingAppInfo: CallingAppInfo,
+ ) {
+ // [START android_identity_credential_provider_user_selection_password]
+ val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
+
+ val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption
+
+ val username = passwordOption.allowedUserIds.first()
+ // Fetch the credentials for the calling app package name
+ val creds = mDatabase.getCredentials(callingAppInfo.packageName)
+ val passwords = creds.passwords
+ val it = passwords.iterator()
+ var password = ""
+ while (it.hasNext()) {
+ val passwordItemCurrent = it.next()
+ if (passwordItemCurrent.username == username) {
+ password = passwordItemCurrent.password
+ break
+ }
+ }
+ // [END android_identity_credential_provider_user_selection_password]
+
+ // [START android_identity_credential_provider_set_response]
+ // Set the response back
+ val result = Intent()
+ val passwordCredential = PasswordCredential(username, password)
+ PendingIntentHandler.setGetCredentialResponse(
+ result, GetCredentialResponse(passwordCredential)
+ )
+ setResult(Activity.RESULT_OK, result)
+ finish()
+ // [END android_identity_credential_provider_set_response]
+ }
+
+ // [START android_identity_credential_pending_intent]
+ fun createSettingsPendingIntent(): PendingIntent
+ // [END android_identity_credential_pending_intent]
+ {
+ return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ }
+
+ private fun getPublicKeyFromKeyPair(keyPair: KeyPair): ByteArray {
+ return byteArrayOf()
+ }
+
+ private fun b64Encode(certHash: ByteArray) {}
+
+ private fun handleCreatePasskeyQuery(
+ request: BeginCreatePublicKeyCredentialRequest
+ ): BeginCreateCredentialResponse {
+ return BeginCreateCredentialResponse()
+ }
+
+ private fun createNewPendingIntent(
+ accountId: String,
+ intent: String
+ ): PendingIntent {
+ return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ }
+
+ private fun b64Decode(encodedString: String): ByteArray {
+ return byteArrayOf()
+ }
+
+ private fun convertPrivateKey(privateKeyBytes: ByteArray): ECPrivateKey {
+ return ECPrivateKeyImpl()
+ }
+}
+
+object CredentialsRepo {
+ const val EXTRA_KEY_ACCOUNT_ID: String = ""
+}
+
+class MyDatabase {
+ fun addNewPassword(passwordInfo: PasswordInfo) {}
+
+ fun getPasskey(credIdEnc: String): PasskeyInfo {
+ return PasskeyInfo()
+ }
+
+ fun getCredentials(packageName: String): CredentialsInfo {
+ return CredentialsInfo()
+ }
+}
+
+data class PasswordInfo(
+ val id: String = "",
+ val password: String = "",
+ val packageName: String = "",
+ val username: String = ""
+)
+
+data class PasskeyInfo(
+ val credPrivateKey: String = "",
+ val uid: String = "",
+ val username: String = ""
+)
+
+data class CredentialsInfo(
+ val passwords: List = listOf()
+)
+
+class ECPrivateKeyImpl: ECPrivateKey {
+ override fun getAlgorithm(): String = ""
+ override fun getFormat(): String = ""
+ override fun getEncoded(): ByteArray = byteArrayOf()
+ override fun getParams(): ECParameterSpec {
+ return ECParameterSpec(
+ EllipticCurve(
+ { 0 },
+ BigInteger.ZERO,
+ BigInteger.ZERO
+ ),
+ ECPoint(
+ BigInteger.ZERO,
+ BigInteger.ZERO
+ ),
+ BigInteger.ZERO,
+ 0
+ )
+ }
+ override fun getS(): BigInteger = BigInteger.ZERO
+}
diff --git a/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt
new file mode 100644
index 00000000..50beedae
--- /dev/null
+++ b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt
@@ -0,0 +1,304 @@
+package com.example.identity.credentialmanager
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.Build.VERSION_CODES
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.OutcomeReceiver
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.CreateCredentialUnknownException
+import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialUnknownException
+import androidx.credentials.provider.AuthenticationAction
+import androidx.credentials.provider.BeginCreateCredentialRequest
+import androidx.credentials.provider.BeginCreateCredentialResponse
+import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
+import androidx.credentials.provider.BeginGetCredentialRequest
+import androidx.credentials.provider.BeginGetCredentialResponse
+import androidx.credentials.provider.BeginGetPasswordOption
+import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
+import androidx.credentials.provider.CallingAppInfo
+import androidx.credentials.provider.CreateEntry
+import androidx.credentials.provider.CredentialEntry
+import androidx.credentials.provider.CredentialProviderService
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.ProviderClearCredentialStateRequest
+import androidx.credentials.provider.PublicKeyCredentialEntry
+import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions
+
+@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
+class MyCredentialProviderService: CredentialProviderService() {
+ private val PERSONAL_ACCOUNT_ID: String = ""
+ private val FAMILY_ACCOUNT_ID: String = ""
+ private val CREATE_PASSKEY_INTENT: String = ""
+ private val PACKAGE_NAME: String = ""
+ private val EXTRA_KEY_ACCOUNT_ID: String = ""
+ private val UNIQUE_REQ_CODE: Int = 1
+ private val UNLOCK_INTENT: String = ""
+ private val UNIQUE_REQUEST_CODE: Int = 0
+ private val TAG: String = ""
+ private val GET_PASSWORD_INTENT: String = ""
+
+ // [START android_identity_credential_provider_passkey_creation]
+ override fun onBeginCreateCredentialRequest(
+ request: BeginCreateCredentialRequest,
+ cancellationSignal: CancellationSignal,
+ callback: OutcomeReceiver,
+ ) {
+ val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
+ if (response != null) {
+ callback.onResult(response)
+ } else {
+ callback.onError(CreateCredentialUnknownException())
+ }
+ }
+
+ fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
+ when (request) {
+ is BeginCreatePublicKeyCredentialRequest -> {
+ // Request is passkey type
+ return handleCreatePasskeyQuery(request)
+ }
+ }
+ // Request not supported
+ return null
+ }
+
+ private fun handleCreatePasskeyQuery(
+ request: BeginCreatePublicKeyCredentialRequest
+ ): BeginCreateCredentialResponse {
+
+ // Adding two create entries - one for storing credentials to the 'Personal'
+ // account, and one for storing them to the 'Family' account. These
+ // accounts are local to this sample app only.
+ val createEntries: MutableList = mutableListOf()
+ createEntries.add( CreateEntry(
+ PERSONAL_ACCOUNT_ID,
+ createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
+ ))
+
+ createEntries.add( CreateEntry(
+ FAMILY_ACCOUNT_ID,
+ createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
+ ))
+
+ return BeginCreateCredentialResponse(createEntries)
+ }
+
+ private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
+ val intent = Intent(action).setPackage(PACKAGE_NAME)
+
+ // Add your local account ID as an extra to the intent, so that when
+ // user selects this entry, the credential can be saved to this
+ // account
+ intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
+
+ return PendingIntent.getActivity(
+ applicationContext, UNIQUE_REQ_CODE,
+ intent, (
+ PendingIntent.FLAG_MUTABLE
+ or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ )
+ }
+ // [END android_identity_credential_provider_passkey_creation]
+
+ private lateinit var response: BeginGetCredentialResponse
+
+ // [START android_identity_credential_provider_sign_in]
+ private val unlockEntryTitle = "Authenticate to continue"
+
+ override fun onBeginGetCredentialRequest(
+ request: BeginGetCredentialRequest,
+ cancellationSignal: CancellationSignal,
+ callback: OutcomeReceiver,
+ ) {
+ if (isAppLocked()) {
+ callback.onResult(BeginGetCredentialResponse(
+ authenticationActions = mutableListOf(
+ AuthenticationAction(
+ unlockEntryTitle, createUnlockPendingIntent())
+ )
+ )
+ )
+ return
+ }
+ try {
+ response = processGetCredentialRequest(request)
+ callback.onResult(response)
+ } catch (e: GetCredentialException) {
+ callback.onError(GetCredentialUnknownException())
+ }
+ }
+ // [END android_identity_credential_provider_sign_in]
+
+ // [START android_identity_credential_provider_unlock_pending_intent]
+ private fun createUnlockPendingIntent(): PendingIntent {
+ val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME)
+ return PendingIntent.getActivity(
+ applicationContext, UNIQUE_REQUEST_CODE, intent, (
+ PendingIntent.FLAG_MUTABLE
+ or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ )
+ }
+ // [END android_identity_credential_provider_unlock_pending_intent]
+
+ // [START android_identity_credential_provider_process_get_credential_request]
+ companion object {
+ // These intent actions are specified for corresponding activities
+ // that are to be invoked through the PendingIntent(s)
+ private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY"
+ private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD"
+
+ }
+
+ fun processGetCredentialRequest(
+ request: BeginGetCredentialRequest
+ ): BeginGetCredentialResponse {
+ val callingPackageInfo = request.callingAppInfo
+ val callingPackageName = callingPackageInfo?.packageName.orEmpty()
+ val credentialEntries: MutableList = mutableListOf()
+
+ for (option in request.beginGetCredentialOptions) {
+ when (option) {
+ is BeginGetPasswordOption -> {
+ credentialEntries.addAll(
+ populatePasswordData(
+ callingPackageName,
+ option
+ )
+ )
+ }
+ is BeginGetPublicKeyCredentialOption -> {
+ credentialEntries.addAll(
+ populatePasskeyData(
+ callingPackageInfo,
+ option
+ )
+ )
+ } else -> {
+ Log.i(TAG, "Request not supported")
+ }
+ }
+ }
+ return BeginGetCredentialResponse(credentialEntries)
+ }
+ // [END android_identity_credential_provider_process_get_credential_request]
+
+ @SuppressLint("RestrictedApi")
+ // [START android_identity_credential_provider_populate_pkpw_data]
+ private fun populatePasskeyData(
+ callingAppInfo: CallingAppInfo?,
+ option: BeginGetPublicKeyCredentialOption
+ ): List {
+ val passkeyEntries: MutableList = mutableListOf()
+ val request = PublicKeyCredentialRequestOptions(option.requestJson)
+ // Get your credentials from database where you saved during creation flow
+ val creds = getCredentialsFromInternalDb(request.rpId)
+ val passkeys = creds.passkeys
+ for (passkey in passkeys) {
+ val data = Bundle()
+ data.putString("credId", passkey.credId)
+ passkeyEntries.add(
+ PublicKeyCredentialEntry(
+ context = applicationContext,
+ username = passkey.username,
+ pendingIntent = createNewPendingIntent(
+ GET_PASSKEY_INTENT_ACTION,
+ data
+ ),
+ beginGetPublicKeyCredentialOption = option,
+ displayName = passkey.displayName,
+ icon = passkey.icon
+ )
+ )
+ }
+ return passkeyEntries
+ }
+
+ // Fetch password credentials and create password entries to populate to the user
+ private fun populatePasswordData(
+ callingPackage: String,
+ option: BeginGetPasswordOption
+ ): List {
+ val passwordEntries: MutableList = mutableListOf()
+
+ // Get your password credentials from database where you saved during
+ // creation flow
+ val creds = getCredentialsFromInternalDb(callingPackage)
+ val passwords = creds.passwords
+ for (password in passwords) {
+ passwordEntries.add(
+ PasswordCredentialEntry(
+ context = applicationContext,
+ username = password.username,
+ pendingIntent = createNewPendingIntent(
+ GET_PASSWORD_INTENT
+ ),
+ beginGetPasswordOption = option,
+ displayName = password.username,
+ icon = password.icon
+ )
+ )
+ }
+ return passwordEntries
+ }
+
+ private fun createNewPendingIntent(
+ action: String,
+ extra: Bundle? = null
+ ): PendingIntent {
+ val intent = Intent(action).setPackage(PACKAGE_NAME)
+ if (extra != null) {
+ intent.putExtra("CREDENTIAL_DATA", extra)
+ }
+
+ return PendingIntent.getActivity(
+ applicationContext, UNIQUE_REQUEST_CODE, intent,
+ (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ )
+ }
+ // [END android_identity_credential_provider_populate_pkpw_data]
+
+ // [START android_identity_credential_provider_clear_credential]
+ override fun onClearCredentialStateRequest(
+ request: ProviderClearCredentialStateRequest,
+ cancellationSignal: CancellationSignal,
+ callback: OutcomeReceiver
+ ) {
+ // Delete any maintained state as appropriate.
+ }
+ // [END android_identity_credential_provider_clear_credential]
+
+ private fun isAppLocked(): Boolean {
+ return true
+ }
+
+ private fun getCredentialsFromInternalDb(rpId: String): Creds {
+ return Creds()
+ }
+}
+
+data class Creds(
+ val passkeys: List = listOf(),
+ val passwords: List = listOf()
+)
+
+data class Passkey(
+ val credId: String = "",
+ val username: String = "",
+ val displayName: String = "",
+ val icon: Icon
+)
+
+data class Password(
+ val username: String = "",
+ val icon: Icon
+)
diff --git a/identity/credentialmanager/src/main/res/xml/provider.xml b/identity/credentialmanager/src/main/res/xml/provider.xml
new file mode 100644
index 00000000..81bdbd01
--- /dev/null
+++ b/identity/credentialmanager/src/main/res/xml/provider.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/identity/credentialmanager/src/main/res/xml/provider_settings.xml b/identity/credentialmanager/src/main/res/xml/provider_settings.xml
new file mode 100644
index 00000000..698ba5f6
--- /dev/null
+++ b/identity/credentialmanager/src/main/res/xml/provider_settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+