Skip to content

Commit 39f2817

Browse files
neelanshsahaiNeelansh Sahai
and
Neelansh Sahai
authoredApr 22, 2025··
Add snippets for Credential Provider (#501)
Co-authored-by: Neelansh Sahai <[email protected]>
1 parent e5dfba3 commit 39f2817

File tree

6 files changed

+841
-2
lines changed

6 files changed

+841
-2
lines changed
 

‎identity/credentialmanager/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ dependencies {
5050
implementation(libs.androidx.compose.ui.graphics)
5151
implementation(libs.androidx.compose.ui.tooling.preview)
5252
implementation(libs.androidx.compose.material3)
53+
54+
// [START android_identity_credman_dependency]
55+
implementation(libs.androidx.credentials)
56+
// [END android_identity_credman_dependency]
57+
5358
// [START android_identity_gradle_dependencies]
5459
implementation(libs.androidx.credentials)
5560

‎identity/credentialmanager/src/main/AndroidManifest.xml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
~ limitations under the License.
1616
-->
1717

18-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
18+
<manifest xmlns:tools="http://schemas.android.com/tools"
19+
xmlns:android="http://schemas.android.com/apk/res/android">
1920

2021
<application
2122
android:allowBackup="true"
@@ -38,6 +39,24 @@
3839
<!-- // [START android_identity_assetlinks_manifest] -->
3940
<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
4041
<!-- // [END android_identity_assetlinks_manifest] -->
42+
43+
<!-- // [START android_identity_credential_provider_service_manifest] -->
44+
<service android:name=".MyCredentialProviderService"
45+
android:enabled="true"
46+
android:exported="true"
47+
android:label="My Credential Provider"
48+
android:icon="<any drawable icon>"
49+
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
50+
tools:targetApi="upside_down_cake">
51+
<intent-filter>
52+
<action android:name="android.service.credentials.CredentialProviderService"/>
53+
</intent-filter>
54+
<meta-data
55+
android:name="android.credentials.provider"
56+
android:resource="@xml/provider"/>
57+
</service>
58+
<!-- // [END android_identity_credential_provider_service_manifest] -->
59+
4160
</application>
4261

43-
</manifest>
62+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,491 @@
1+
package com.example.identity.credentialmanager
2+
3+
import android.annotation.SuppressLint
4+
import android.app.Activity
5+
import android.app.PendingIntent
6+
import android.content.Intent
7+
import android.os.Build.VERSION_CODES
8+
import android.os.Bundle
9+
import android.os.PersistableBundle
10+
import androidx.annotation.RequiresApi
11+
import androidx.biometric.BiometricManager
12+
import androidx.biometric.BiometricPrompt
13+
import androidx.biometric.BiometricPrompt.AuthenticationCallback
14+
import androidx.biometric.BiometricPrompt.AuthenticationResult
15+
import androidx.credentials.CreatePasswordRequest
16+
import androidx.credentials.CreatePasswordResponse
17+
import androidx.credentials.CreatePublicKeyCredentialRequest
18+
import androidx.credentials.CreatePublicKeyCredentialResponse
19+
import androidx.credentials.GetCredentialResponse
20+
import androidx.credentials.GetPasswordOption
21+
import androidx.credentials.GetPublicKeyCredentialOption
22+
import androidx.credentials.PasswordCredential
23+
import androidx.credentials.PublicKeyCredential
24+
import androidx.credentials.provider.BeginCreateCredentialRequest
25+
import androidx.credentials.provider.BeginCreateCredentialResponse
26+
import androidx.credentials.provider.BeginCreatePasswordCredentialRequest
27+
import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
28+
import androidx.credentials.provider.CallingAppInfo
29+
import androidx.credentials.provider.CreateEntry
30+
import androidx.credentials.provider.PendingIntentHandler
31+
import androidx.credentials.webauthn.AuthenticatorAssertionResponse
32+
import androidx.credentials.webauthn.AuthenticatorAttestationResponse
33+
import androidx.credentials.webauthn.FidoPublicKeyCredential
34+
import androidx.credentials.webauthn.PublicKeyCredentialCreationOptions
35+
import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions
36+
import androidx.fragment.app.FragmentActivity
37+
import java.math.BigInteger
38+
import java.security.KeyPair
39+
import java.security.KeyPairGenerator
40+
import java.security.MessageDigest
41+
import java.security.SecureRandom
42+
import java.security.Signature
43+
import java.security.interfaces.ECPrivateKey
44+
import java.security.spec.ECGenParameterSpec
45+
import java.security.spec.ECParameterSpec
46+
import java.security.spec.ECPoint
47+
import java.security.spec.EllipticCurve
48+
49+
class CredentialProviderDummyActivity: FragmentActivity() {
50+
51+
private val PERSONAL_ACCOUNT_ID: String = ""
52+
private val FAMILY_ACCOUNT_ID: String = ""
53+
private val CREATE_PASSWORD_INTENT: String = ""
54+
55+
@RequiresApi(VERSION_CODES.M)
56+
// [START android_identity_credential_provider_handle_passkey]
57+
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
58+
super.onCreate(savedInstanceState, persistentState)
59+
// ...
60+
61+
val request =
62+
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
63+
64+
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
65+
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
66+
val publicKeyRequest: CreatePublicKeyCredentialRequest =
67+
request.callingRequest as CreatePublicKeyCredentialRequest
68+
createPasskey(
69+
publicKeyRequest.requestJson,
70+
request.callingAppInfo,
71+
publicKeyRequest.clientDataHash,
72+
accountId
73+
)
74+
}
75+
}
76+
77+
@SuppressLint("RestrictedApi")
78+
fun createPasskey(
79+
requestJson: String,
80+
callingAppInfo: CallingAppInfo?,
81+
clientDataHash: ByteArray?,
82+
accountId: String?
83+
) {
84+
val request = PublicKeyCredentialCreationOptions(requestJson)
85+
86+
val biometricPrompt = BiometricPrompt(
87+
this,
88+
{ }, // Pass in your own executor
89+
object : AuthenticationCallback() {
90+
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
91+
super.onAuthenticationError(errorCode, errString)
92+
finish()
93+
}
94+
95+
override fun onAuthenticationFailed() {
96+
super.onAuthenticationFailed()
97+
finish()
98+
}
99+
100+
@RequiresApi(VERSION_CODES.P)
101+
override fun onAuthenticationSucceeded(
102+
result: AuthenticationResult
103+
) {
104+
super.onAuthenticationSucceeded(result)
105+
106+
// Generate a credentialId
107+
val credentialId = ByteArray(32)
108+
SecureRandom().nextBytes(credentialId)
109+
110+
// Generate a credential key pair
111+
val spec = ECGenParameterSpec("secp256r1")
112+
val keyPairGen = KeyPairGenerator.getInstance("EC");
113+
keyPairGen.initialize(spec)
114+
val keyPair = keyPairGen.genKeyPair()
115+
116+
// Save passkey in your database as per your own implementation
117+
118+
// Create AuthenticatorAttestationResponse object to pass to
119+
// FidoPublicKeyCredential
120+
121+
val response = AuthenticatorAttestationResponse(
122+
requestOptions = request,
123+
credentialId = credentialId,
124+
credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
125+
origin = appInfoToOrigin(callingAppInfo!!),
126+
up = true,
127+
uv = true,
128+
be = true,
129+
bs = true,
130+
packageName = callingAppInfo.packageName
131+
)
132+
133+
val credential = FidoPublicKeyCredential(
134+
rawId = credentialId,
135+
response = response,
136+
authenticatorAttachment = "", // Add your authenticator attachment
137+
)
138+
val result = Intent()
139+
140+
val createPublicKeyCredResponse =
141+
CreatePublicKeyCredentialResponse(credential.json())
142+
143+
// Set the CreateCredentialResponse as the result of the Activity
144+
PendingIntentHandler.setCreateCredentialResponse(
145+
result,
146+
createPublicKeyCredResponse
147+
)
148+
setResult(RESULT_OK, result)
149+
finish()
150+
}
151+
}
152+
)
153+
154+
val promptInfo = BiometricPrompt.PromptInfo.Builder()
155+
.setTitle("Use your screen lock")
156+
.setSubtitle("Create passkey for ${request.rp.name}")
157+
.setAllowedAuthenticators(
158+
BiometricManager.Authenticators.BIOMETRIC_STRONG
159+
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
160+
)
161+
.build()
162+
biometricPrompt.authenticate(promptInfo)
163+
}
164+
165+
@RequiresApi(VERSION_CODES.P)
166+
fun appInfoToOrigin(info: CallingAppInfo): String {
167+
val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
168+
val md = MessageDigest.getInstance("SHA-256");
169+
val certHash = md.digest(cert)
170+
// This is the format for origin
171+
return "android:apk-key-hash:${b64Encode(certHash)}"
172+
}
173+
// [END android_identity_credential_provider_handle_passkey]
174+
175+
@RequiresApi(VERSION_CODES.M)
176+
// [START android_identity_credential_provider_password_creation]
177+
fun processCreateCredentialRequest(
178+
request: BeginCreateCredentialRequest
179+
): BeginCreateCredentialResponse? {
180+
when (request) {
181+
is BeginCreatePublicKeyCredentialRequest -> {
182+
// Request is passkey type
183+
return handleCreatePasskeyQuery(request)
184+
}
185+
186+
is BeginCreatePasswordCredentialRequest -> {
187+
// Request is password type
188+
return handleCreatePasswordQuery(request)
189+
}
190+
}
191+
return null
192+
}
193+
194+
@RequiresApi(VERSION_CODES.M)
195+
private fun handleCreatePasswordQuery(
196+
request: BeginCreatePasswordCredentialRequest
197+
): BeginCreateCredentialResponse {
198+
val createEntries: MutableList<CreateEntry> = mutableListOf()
199+
200+
// Adding two create entries - one for storing credentials to the 'Personal'
201+
// account, and one for storing them to the 'Family' account. These
202+
// accounts are local to this sample app only.
203+
createEntries.add(
204+
CreateEntry(
205+
PERSONAL_ACCOUNT_ID,
206+
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
207+
)
208+
)
209+
createEntries.add(
210+
CreateEntry(
211+
FAMILY_ACCOUNT_ID,
212+
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
213+
)
214+
)
215+
216+
return BeginCreateCredentialResponse(createEntries)
217+
}
218+
// [END android_identity_credential_provider_password_creation]
219+
220+
@RequiresApi(VERSION_CODES.M)
221+
fun handleEntrySelectionForPasswordCreation(
222+
mDatabase: MyDatabase
223+
) {
224+
// [START android_identity_credential_provider_entry_selection_password_creation]
225+
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
226+
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
227+
228+
if (createRequest == null) {
229+
return
230+
}
231+
232+
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
233+
234+
// Fetch the ID and password from the request and save it in your database
235+
mDatabase.addNewPassword(
236+
PasswordInfo(
237+
request.id,
238+
request.password,
239+
createRequest.callingAppInfo.packageName
240+
)
241+
)
242+
243+
//Set the final response back
244+
val result = Intent()
245+
val response = CreatePasswordResponse()
246+
PendingIntentHandler.setCreateCredentialResponse(result, response)
247+
setResult(Activity.RESULT_OK, result)
248+
finish()
249+
// [END android_identity_credential_provider_entry_selection_password_creation]
250+
}
251+
252+
@RequiresApi(VERSION_CODES.P)
253+
private fun handleUserSelectionForPasskeys(
254+
mDatabase: MyDatabase
255+
) {
256+
// [START android_identity_credential_provider_user_pk_selection]
257+
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
258+
val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption
259+
260+
val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
261+
val credIdEnc = requestInfo?.getString("credId").orEmpty()
262+
263+
// Get the saved passkey from your database based on the credential ID from the PublicKeyRequest
264+
val passkey = mDatabase.getPasskey(credIdEnc)
265+
266+
// Decode the credential ID, private key and user ID
267+
val credId = b64Decode(credIdEnc)
268+
val privateKey = b64Decode(passkey.credPrivateKey)
269+
val uid = b64Decode(passkey.uid)
270+
271+
val origin = appInfoToOrigin(getRequest.callingAppInfo)
272+
val packageName = getRequest.callingAppInfo.packageName
273+
274+
validatePasskey(
275+
publicKeyRequest.requestJson,
276+
origin,
277+
packageName,
278+
uid,
279+
passkey.username,
280+
credId,
281+
privateKey
282+
)
283+
// [END android_identity_credential_provider_user_pk_selection]
284+
}
285+
286+
@SuppressLint("RestrictedApi")
287+
@RequiresApi(VERSION_CODES.M)
288+
private fun validatePasskey(
289+
requestJson: String,
290+
origin: String,
291+
packageName: String,
292+
uid: ByteArray,
293+
username: String,
294+
credId: ByteArray,
295+
privateKeyBytes: ByteArray,
296+
) {
297+
// [START android_identity_credential_provider_user_validation_biometric]
298+
val request = PublicKeyCredentialRequestOptions(requestJson)
299+
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
300+
301+
val biometricPrompt = BiometricPrompt(
302+
this,
303+
{ }, // Pass in your own executor
304+
object : BiometricPrompt.AuthenticationCallback() {
305+
override fun onAuthenticationError(
306+
errorCode: Int, errString: CharSequence
307+
) {
308+
super.onAuthenticationError(errorCode, errString)
309+
finish()
310+
}
311+
312+
override fun onAuthenticationFailed() {
313+
super.onAuthenticationFailed()
314+
finish()
315+
}
316+
317+
override fun onAuthenticationSucceeded(
318+
result: BiometricPrompt.AuthenticationResult
319+
) {
320+
super.onAuthenticationSucceeded(result)
321+
val response = AuthenticatorAssertionResponse(
322+
requestOptions = request,
323+
credentialId = credId,
324+
origin = origin,
325+
up = true,
326+
uv = true,
327+
be = true,
328+
bs = true,
329+
userHandle = uid,
330+
packageName = packageName
331+
)
332+
333+
val sig = Signature.getInstance("SHA256withECDSA");
334+
sig.initSign(privateKey)
335+
sig.update(response.dataToSign())
336+
response.signature = sig.sign()
337+
338+
val credential = FidoPublicKeyCredential(
339+
rawId = credId,
340+
response = response,
341+
authenticatorAttachment = "", // Add your authenticator attachment
342+
)
343+
val result = Intent()
344+
val passkeyCredential = PublicKeyCredential(credential.json())
345+
PendingIntentHandler.setGetCredentialResponse(
346+
result, GetCredentialResponse(passkeyCredential)
347+
)
348+
setResult(RESULT_OK, result)
349+
finish()
350+
}
351+
}
352+
)
353+
354+
val promptInfo = BiometricPrompt.PromptInfo.Builder()
355+
.setTitle("Use your screen lock")
356+
.setSubtitle("Use passkey for ${request.rpId}")
357+
.setAllowedAuthenticators(
358+
BiometricManager.Authenticators.BIOMETRIC_STRONG
359+
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
360+
)
361+
.build()
362+
biometricPrompt.authenticate(promptInfo)
363+
// [END android_identity_credential_provider_user_validation_biometric]
364+
}
365+
366+
@RequiresApi(VERSION_CODES.M)
367+
private fun handleUserSelectionForPasswordAuthentication(
368+
mDatabase: MyDatabase,
369+
callingAppInfo: CallingAppInfo,
370+
) {
371+
// [START android_identity_credential_provider_user_selection_password]
372+
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
373+
374+
val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption
375+
376+
val username = passwordOption.allowedUserIds.first()
377+
// Fetch the credentials for the calling app package name
378+
val creds = mDatabase.getCredentials(callingAppInfo.packageName)
379+
val passwords = creds.passwords
380+
val it = passwords.iterator()
381+
var password = ""
382+
while (it.hasNext()) {
383+
val passwordItemCurrent = it.next()
384+
if (passwordItemCurrent.username == username) {
385+
password = passwordItemCurrent.password
386+
break
387+
}
388+
}
389+
// [END android_identity_credential_provider_user_selection_password]
390+
391+
// [START android_identity_credential_provider_set_response]
392+
// Set the response back
393+
val result = Intent()
394+
val passwordCredential = PasswordCredential(username, password)
395+
PendingIntentHandler.setGetCredentialResponse(
396+
result, GetCredentialResponse(passwordCredential)
397+
)
398+
setResult(Activity.RESULT_OK, result)
399+
finish()
400+
// [END android_identity_credential_provider_set_response]
401+
}
402+
403+
// [START android_identity_credential_pending_intent]
404+
fun createSettingsPendingIntent(): PendingIntent
405+
// [END android_identity_credential_pending_intent]
406+
{
407+
return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
408+
}
409+
410+
private fun getPublicKeyFromKeyPair(keyPair: KeyPair): ByteArray {
411+
return byteArrayOf()
412+
}
413+
414+
private fun b64Encode(certHash: ByteArray) {}
415+
416+
private fun handleCreatePasskeyQuery(
417+
request: BeginCreatePublicKeyCredentialRequest
418+
): BeginCreateCredentialResponse {
419+
return BeginCreateCredentialResponse()
420+
}
421+
422+
private fun createNewPendingIntent(
423+
accountId: String,
424+
intent: String
425+
): PendingIntent {
426+
return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
427+
}
428+
429+
private fun b64Decode(encodedString: String): ByteArray {
430+
return byteArrayOf()
431+
}
432+
433+
private fun convertPrivateKey(privateKeyBytes: ByteArray): ECPrivateKey {
434+
return ECPrivateKeyImpl()
435+
}
436+
}
437+
438+
object CredentialsRepo {
439+
const val EXTRA_KEY_ACCOUNT_ID: String = ""
440+
}
441+
442+
class MyDatabase {
443+
fun addNewPassword(passwordInfo: PasswordInfo) {}
444+
445+
fun getPasskey(credIdEnc: String): PasskeyInfo {
446+
return PasskeyInfo()
447+
}
448+
449+
fun getCredentials(packageName: String): CredentialsInfo {
450+
return CredentialsInfo()
451+
}
452+
}
453+
454+
data class PasswordInfo(
455+
val id: String = "",
456+
val password: String = "",
457+
val packageName: String = "",
458+
val username: String = ""
459+
)
460+
461+
data class PasskeyInfo(
462+
val credPrivateKey: String = "",
463+
val uid: String = "",
464+
val username: String = ""
465+
)
466+
467+
data class CredentialsInfo(
468+
val passwords: List<PasswordInfo> = listOf()
469+
)
470+
471+
class ECPrivateKeyImpl: ECPrivateKey {
472+
override fun getAlgorithm(): String = ""
473+
override fun getFormat(): String = ""
474+
override fun getEncoded(): ByteArray = byteArrayOf()
475+
override fun getParams(): ECParameterSpec {
476+
return ECParameterSpec(
477+
EllipticCurve(
478+
{ 0 },
479+
BigInteger.ZERO,
480+
BigInteger.ZERO
481+
),
482+
ECPoint(
483+
BigInteger.ZERO,
484+
BigInteger.ZERO
485+
),
486+
BigInteger.ZERO,
487+
0
488+
)
489+
}
490+
override fun getS(): BigInteger = BigInteger.ZERO
491+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
package com.example.identity.credentialmanager
2+
3+
import android.annotation.SuppressLint
4+
import android.app.PendingIntent
5+
import android.content.Intent
6+
import android.graphics.drawable.Icon
7+
import android.os.Build.VERSION_CODES
8+
import android.os.Bundle
9+
import android.os.CancellationSignal
10+
import android.os.OutcomeReceiver
11+
import android.util.Log
12+
import androidx.annotation.RequiresApi
13+
import androidx.credentials.exceptions.ClearCredentialException
14+
import androidx.credentials.exceptions.CreateCredentialException
15+
import androidx.credentials.exceptions.CreateCredentialUnknownException
16+
import androidx.credentials.exceptions.GetCredentialException
17+
import androidx.credentials.exceptions.GetCredentialUnknownException
18+
import androidx.credentials.provider.AuthenticationAction
19+
import androidx.credentials.provider.BeginCreateCredentialRequest
20+
import androidx.credentials.provider.BeginCreateCredentialResponse
21+
import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
22+
import androidx.credentials.provider.BeginGetCredentialRequest
23+
import androidx.credentials.provider.BeginGetCredentialResponse
24+
import androidx.credentials.provider.BeginGetPasswordOption
25+
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
26+
import androidx.credentials.provider.CallingAppInfo
27+
import androidx.credentials.provider.CreateEntry
28+
import androidx.credentials.provider.CredentialEntry
29+
import androidx.credentials.provider.CredentialProviderService
30+
import androidx.credentials.provider.PasswordCredentialEntry
31+
import androidx.credentials.provider.ProviderClearCredentialStateRequest
32+
import androidx.credentials.provider.PublicKeyCredentialEntry
33+
import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions
34+
35+
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
36+
class MyCredentialProviderService: CredentialProviderService() {
37+
private val PERSONAL_ACCOUNT_ID: String = ""
38+
private val FAMILY_ACCOUNT_ID: String = ""
39+
private val CREATE_PASSKEY_INTENT: String = ""
40+
private val PACKAGE_NAME: String = ""
41+
private val EXTRA_KEY_ACCOUNT_ID: String = ""
42+
private val UNIQUE_REQ_CODE: Int = 1
43+
private val UNLOCK_INTENT: String = ""
44+
private val UNIQUE_REQUEST_CODE: Int = 0
45+
private val TAG: String = ""
46+
private val GET_PASSWORD_INTENT: String = ""
47+
48+
// [START android_identity_credential_provider_passkey_creation]
49+
override fun onBeginCreateCredentialRequest(
50+
request: BeginCreateCredentialRequest,
51+
cancellationSignal: CancellationSignal,
52+
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
53+
) {
54+
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
55+
if (response != null) {
56+
callback.onResult(response)
57+
} else {
58+
callback.onError(CreateCredentialUnknownException())
59+
}
60+
}
61+
62+
fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
63+
when (request) {
64+
is BeginCreatePublicKeyCredentialRequest -> {
65+
// Request is passkey type
66+
return handleCreatePasskeyQuery(request)
67+
}
68+
}
69+
// Request not supported
70+
return null
71+
}
72+
73+
private fun handleCreatePasskeyQuery(
74+
request: BeginCreatePublicKeyCredentialRequest
75+
): BeginCreateCredentialResponse {
76+
77+
// Adding two create entries - one for storing credentials to the 'Personal'
78+
// account, and one for storing them to the 'Family' account. These
79+
// accounts are local to this sample app only.
80+
val createEntries: MutableList<CreateEntry> = mutableListOf()
81+
createEntries.add( CreateEntry(
82+
PERSONAL_ACCOUNT_ID,
83+
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
84+
))
85+
86+
createEntries.add( CreateEntry(
87+
FAMILY_ACCOUNT_ID,
88+
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
89+
))
90+
91+
return BeginCreateCredentialResponse(createEntries)
92+
}
93+
94+
private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
95+
val intent = Intent(action).setPackage(PACKAGE_NAME)
96+
97+
// Add your local account ID as an extra to the intent, so that when
98+
// user selects this entry, the credential can be saved to this
99+
// account
100+
intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
101+
102+
return PendingIntent.getActivity(
103+
applicationContext, UNIQUE_REQ_CODE,
104+
intent, (
105+
PendingIntent.FLAG_MUTABLE
106+
or PendingIntent.FLAG_UPDATE_CURRENT
107+
)
108+
)
109+
}
110+
// [END android_identity_credential_provider_passkey_creation]
111+
112+
private lateinit var response: BeginGetCredentialResponse
113+
114+
// [START android_identity_credential_provider_sign_in]
115+
private val unlockEntryTitle = "Authenticate to continue"
116+
117+
override fun onBeginGetCredentialRequest(
118+
request: BeginGetCredentialRequest,
119+
cancellationSignal: CancellationSignal,
120+
callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
121+
) {
122+
if (isAppLocked()) {
123+
callback.onResult(BeginGetCredentialResponse(
124+
authenticationActions = mutableListOf(
125+
AuthenticationAction(
126+
unlockEntryTitle, createUnlockPendingIntent())
127+
)
128+
)
129+
)
130+
return
131+
}
132+
try {
133+
response = processGetCredentialRequest(request)
134+
callback.onResult(response)
135+
} catch (e: GetCredentialException) {
136+
callback.onError(GetCredentialUnknownException())
137+
}
138+
}
139+
// [END android_identity_credential_provider_sign_in]
140+
141+
// [START android_identity_credential_provider_unlock_pending_intent]
142+
private fun createUnlockPendingIntent(): PendingIntent {
143+
val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME)
144+
return PendingIntent.getActivity(
145+
applicationContext, UNIQUE_REQUEST_CODE, intent, (
146+
PendingIntent.FLAG_MUTABLE
147+
or PendingIntent.FLAG_UPDATE_CURRENT
148+
)
149+
)
150+
}
151+
// [END android_identity_credential_provider_unlock_pending_intent]
152+
153+
// [START android_identity_credential_provider_process_get_credential_request]
154+
companion object {
155+
// These intent actions are specified for corresponding activities
156+
// that are to be invoked through the PendingIntent(s)
157+
private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY"
158+
private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD"
159+
160+
}
161+
162+
fun processGetCredentialRequest(
163+
request: BeginGetCredentialRequest
164+
): BeginGetCredentialResponse {
165+
val callingPackageInfo = request.callingAppInfo
166+
val callingPackageName = callingPackageInfo?.packageName.orEmpty()
167+
val credentialEntries: MutableList<CredentialEntry> = mutableListOf()
168+
169+
for (option in request.beginGetCredentialOptions) {
170+
when (option) {
171+
is BeginGetPasswordOption -> {
172+
credentialEntries.addAll(
173+
populatePasswordData(
174+
callingPackageName,
175+
option
176+
)
177+
)
178+
}
179+
is BeginGetPublicKeyCredentialOption -> {
180+
credentialEntries.addAll(
181+
populatePasskeyData(
182+
callingPackageInfo,
183+
option
184+
)
185+
)
186+
} else -> {
187+
Log.i(TAG, "Request not supported")
188+
}
189+
}
190+
}
191+
return BeginGetCredentialResponse(credentialEntries)
192+
}
193+
// [END android_identity_credential_provider_process_get_credential_request]
194+
195+
@SuppressLint("RestrictedApi")
196+
// [START android_identity_credential_provider_populate_pkpw_data]
197+
private fun populatePasskeyData(
198+
callingAppInfo: CallingAppInfo?,
199+
option: BeginGetPublicKeyCredentialOption
200+
): List<CredentialEntry> {
201+
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
202+
val request = PublicKeyCredentialRequestOptions(option.requestJson)
203+
// Get your credentials from database where you saved during creation flow
204+
val creds = getCredentialsFromInternalDb(request.rpId)
205+
val passkeys = creds.passkeys
206+
for (passkey in passkeys) {
207+
val data = Bundle()
208+
data.putString("credId", passkey.credId)
209+
passkeyEntries.add(
210+
PublicKeyCredentialEntry(
211+
context = applicationContext,
212+
username = passkey.username,
213+
pendingIntent = createNewPendingIntent(
214+
GET_PASSKEY_INTENT_ACTION,
215+
data
216+
),
217+
beginGetPublicKeyCredentialOption = option,
218+
displayName = passkey.displayName,
219+
icon = passkey.icon
220+
)
221+
)
222+
}
223+
return passkeyEntries
224+
}
225+
226+
// Fetch password credentials and create password entries to populate to the user
227+
private fun populatePasswordData(
228+
callingPackage: String,
229+
option: BeginGetPasswordOption
230+
): List<CredentialEntry> {
231+
val passwordEntries: MutableList<CredentialEntry> = mutableListOf()
232+
233+
// Get your password credentials from database where you saved during
234+
// creation flow
235+
val creds = getCredentialsFromInternalDb(callingPackage)
236+
val passwords = creds.passwords
237+
for (password in passwords) {
238+
passwordEntries.add(
239+
PasswordCredentialEntry(
240+
context = applicationContext,
241+
username = password.username,
242+
pendingIntent = createNewPendingIntent(
243+
GET_PASSWORD_INTENT
244+
),
245+
beginGetPasswordOption = option,
246+
displayName = password.username,
247+
icon = password.icon
248+
)
249+
)
250+
}
251+
return passwordEntries
252+
}
253+
254+
private fun createNewPendingIntent(
255+
action: String,
256+
extra: Bundle? = null
257+
): PendingIntent {
258+
val intent = Intent(action).setPackage(PACKAGE_NAME)
259+
if (extra != null) {
260+
intent.putExtra("CREDENTIAL_DATA", extra)
261+
}
262+
263+
return PendingIntent.getActivity(
264+
applicationContext, UNIQUE_REQUEST_CODE, intent,
265+
(PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
266+
)
267+
}
268+
// [END android_identity_credential_provider_populate_pkpw_data]
269+
270+
// [START android_identity_credential_provider_clear_credential]
271+
override fun onClearCredentialStateRequest(
272+
request: ProviderClearCredentialStateRequest,
273+
cancellationSignal: CancellationSignal,
274+
callback: OutcomeReceiver<Void?, ClearCredentialException>
275+
) {
276+
// Delete any maintained state as appropriate.
277+
}
278+
// [END android_identity_credential_provider_clear_credential]
279+
280+
private fun isAppLocked(): Boolean {
281+
return true
282+
}
283+
284+
private fun getCredentialsFromInternalDb(rpId: String): Creds {
285+
return Creds()
286+
}
287+
}
288+
289+
data class Creds(
290+
val passkeys: List<Passkey> = listOf(),
291+
val passwords: List<Password> = listOf()
292+
)
293+
294+
data class Passkey(
295+
val credId: String = "",
296+
val username: String = "",
297+
val displayName: String = "",
298+
val icon: Icon
299+
)
300+
301+
data class Password(
302+
val username: String = "",
303+
val icon: Icon
304+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- // [START android_identity_credential_provider_xml] -->
3+
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
4+
<capabilities>
5+
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
6+
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
7+
</capabilities>
8+
</credential-provider>
9+
<!-- // [END android_identity_credential_provider_xml] -->
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- // [START android_identity_credential_provider_settings] -->
3+
<credential-provider
4+
xmlns:android="http://schemas.android.com/apk/res/android"
5+
android:settingsSubtitle="Example settings provider name"
6+
android:settingsActivity="com.example.SettingsActivity">
7+
<capabilities>
8+
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
9+
</capabilities>
10+
</credential-provider>
11+
<!-- // [END android_identity_credential_provider_settings] -->

0 commit comments

Comments
 (0)
Please sign in to comment.