1package com.peachpasswords.passkeys23import android.os.Build4import android.os.CancellationSignal5import android.graphics.drawable.Icon6import androidx.credentials.provider.BeginCreateCredentialRequest7import androidx.credentials.provider.BeginCreateCredentialResponse8import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest9import androidx.credentials.provider.BeginGetCredentialRequest10import androidx.credentials.provider.BeginGetCredentialResponse11import androidx.credentials.provider.BeginGetPublicKeyCredentialOption12import androidx.credentials.provider.CreateEntry13import androidx.credentials.provider.AuthenticationAction14import androidx.credentials.provider.CredentialProviderService15import androidx.credentials.provider.ProviderClearCredentialStateRequest16import androidx.credentials.provider.PublicKeyCredentialEntry17import android.os.OutcomeReceiver18import androidx.credentials.exceptions.ClearCredentialException19import androidx.credentials.exceptions.CreateCredentialException20import androidx.credentials.exceptions.GetCredentialException21import androidx.annotation.RequiresApi22import com.peachpasswords.R23import com.peachpasswords.vault.VaultRepository24import dagger.hilt.android.AndroidEntryPoint25import java.time.Instant26import javax.inject.Inject2728293031323334353637383940@AndroidEntryPoint41@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)42class PeachCredentialProviderService : CredentialProviderService() {4344 @Inject lateinit var vault: VaultRepository4546 override fun onBeginGetCredentialRequest(47 request: BeginGetCredentialRequest,48 cancellationSignal: CancellationSignal,49 callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,50 ) {51 if (!vault.settings.value.passkeyProviderEnabled) {52 callback.onResult(BeginGetCredentialResponse())53 return54 }55 if (!vault.isUnlocked.value) {56 callback.onResult(57 BeginGetCredentialResponse(58 authenticationActions = listOf(59 AuthenticationAction(60 title = "Unlock Peach",61 pendingIntent = PasskeyUnlockIntent.pendingIntent(this),62 ),63 ),64 ),65 )66 return67 }68 val passkeyOptions = request.beginGetCredentialOptions69 .filterIsInstance<BeginGetPublicKeyCredentialOption>()70 if (passkeyOptions.isEmpty()) {71 callback.onResult(BeginGetCredentialResponse())72 return73 }7475 val icon = Icon.createWithResource(this, R.drawable.ic_peach_mark)76 val entries = passkeyOptions.flatMap { option ->77 PasskeyCredentialPlanner.candidates(vault.getEntries(), option.requestJson).map { candidate ->78 PublicKeyCredentialEntry(79 context = this,80 username = candidate.username,81 pendingIntent = PasskeyCredentialSelectionActivity.pendingIntent(82 this,83 candidate.entryId,84 option.requestJson,85 ),86 beginGetPublicKeyCredentialOption = option,87 displayName = candidate.displayName,88 lastUsedTime = Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L),89 icon = icon,90 isAutoSelectAllowed = false,91 )92 }93 }94 callback.onResult(BeginGetCredentialResponse(credentialEntries = entries))95 }9697 override fun onBeginCreateCredentialRequest(98 request: BeginCreateCredentialRequest,99 cancellationSignal: CancellationSignal,100 callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,101 ) {102 val publicKeyRequest = request as? BeginCreatePublicKeyCredentialRequest103 if (publicKeyRequest == null || !vault.settings.value.passkeyProviderEnabled || !vault.isUnlocked.value) {104 callback.onResult(BeginCreateCredentialResponse())105 return106 }107 val count = vault.getEntries().count { it.passkey != null }108 val entry = CreateEntry(109 accountName = "Peach",110 pendingIntent = PasskeyCredentialSelectionActivity.createPendingIntent(this, publicKeyRequest.requestJson),111 description = "Save passkey to Peach",112 lastUsedTime = Instant.now(),113 icon = Icon.createWithResource(this, R.drawable.ic_peach_mark),114 passwordCredentialCount = null,115 publicKeyCredentialCount = count,116 totalCredentialCount = vault.getEntries().size,117 isAutoSelectAllowed = false,118 )119 callback.onResult(BeginCreateCredentialResponse(createEntries = listOf(entry)))120 }121122 override fun onClearCredentialStateRequest(123 request: ProviderClearCredentialStateRequest,124 cancellationSignal: CancellationSignal,125 callback: OutcomeReceiver<Void?, ClearCredentialException>,126 ) {127 callback.onResult(null)128 }129}