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.CredentialProviderService14import androidx.credentials.provider.ProviderClearCredentialStateRequest15import androidx.credentials.provider.PublicKeyCredentialEntry16import android.os.OutcomeReceiver17import androidx.credentials.exceptions.ClearCredentialException18import androidx.credentials.exceptions.CreateCredentialException19import androidx.credentials.exceptions.GetCredentialException20import androidx.annotation.RequiresApi21import com.peachpasswords.R22import com.peachpasswords.vault.VaultRepository23import dagger.hilt.android.AndroidEntryPoint24import java.time.Instant25import javax.inject.Inject2627282930313233343536373839@AndroidEntryPoint40@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)41class PeachCredentialProviderService : CredentialProviderService() {4243 @Inject lateinit var vault: VaultRepository4445 override fun onBeginGetCredentialRequest(46 request: BeginGetCredentialRequest,47 cancellationSignal: CancellationSignal,48 callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,49 ) {50 if (!vault.settings.value.passkeyProviderEnabled || !vault.isUnlocked.value) {51 callback.onResult(BeginGetCredentialResponse())52 return53 }54 val passkeyOptions = request.beginGetCredentialOptions55 .filterIsInstance<BeginGetPublicKeyCredentialOption>()56 if (passkeyOptions.isEmpty()) {57 callback.onResult(BeginGetCredentialResponse())58 return59 }6061 val icon = Icon.createWithResource(this, R.drawable.ic_peach_mark)62 val entries = passkeyOptions.flatMap { option ->63 PasskeyCredentialPlanner.candidates(vault.getEntries(), option.requestJson).map { candidate ->64 PublicKeyCredentialEntry(65 context = this,66 username = candidate.username,67 pendingIntent = PasskeyCredentialSelectionActivity.pendingIntent(68 this,69 candidate.entryId,70 option.requestJson,71 ),72 beginGetPublicKeyCredentialOption = option,73 displayName = candidate.displayName,74 lastUsedTime = Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L),75 icon = icon,76 isAutoSelectAllowed = false,77 )78 }79 }80 callback.onResult(BeginGetCredentialResponse(credentialEntries = entries))81 }8283 override fun onBeginCreateCredentialRequest(84 request: BeginCreateCredentialRequest,85 cancellationSignal: CancellationSignal,86 callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,87 ) {88 val publicKeyRequest = request as? BeginCreatePublicKeyCredentialRequest89 if (publicKeyRequest == null || !vault.settings.value.passkeyProviderEnabled || !vault.isUnlocked.value) {90 callback.onResult(BeginCreateCredentialResponse())91 return92 }93 val count = vault.getEntries().count { it.passkey != null }94 val entry = CreateEntry(95 accountName = "Peach",96 pendingIntent = PasskeyCredentialSelectionActivity.createPendingIntent(this, publicKeyRequest.requestJson),97 description = "Save passkey to Peach",98 lastUsedTime = Instant.now(),99 icon = Icon.createWithResource(this, R.drawable.ic_peach_mark),100 passwordCredentialCount = null,101 publicKeyCredentialCount = count,102 totalCredentialCount = vault.getEntries().size,103 isAutoSelectAllowed = false,104 )105 callback.onResult(BeginCreateCredentialResponse(createEntries = listOf(entry)))106 }107108 override fun onClearCredentialStateRequest(109 request: ProviderClearCredentialStateRequest,110 cancellationSignal: CancellationSignal,111 callback: OutcomeReceiver<Void?, ClearCredentialException>,112 ) {113 callback.onResult(null)114 }115}