1package com.peachpasswords.credentials23import android.content.Intent4import android.graphics.drawable.Icon5import android.os.Bundle6import com.peachpasswords.BuildConfig7import androidx.activity.compose.setContent8import com.peachpasswords.ui.components.QaFlags9import com.peachpasswords.ui.components.SecureWindowPolicy10import androidx.credentials.provider.BeginGetCredentialRequest11import androidx.credentials.provider.BeginGetPasswordOption12import androidx.credentials.provider.BeginGetPublicKeyCredentialOption13import androidx.credentials.provider.BeginGetCredentialResponse14import androidx.credentials.provider.CredentialEntry15import androidx.credentials.provider.PasswordCredentialEntry16import androidx.credentials.provider.PendingIntentHandler17import androidx.credentials.provider.PublicKeyCredentialEntry18import androidx.fragment.app.FragmentActivity19import com.peachpasswords.R20import com.peachpasswords.passkeys.PasskeyCallerOrigin21import com.peachpasswords.passkeys.PasskeyCredentialPlanner22import com.peachpasswords.passkeys.PasskeyCredentialSelectionActivity23import com.peachpasswords.passkeys.PasskeyUnlockIntent24import com.peachpasswords.autofill.appLinkDomains25import com.peachpasswords.shared.extractDomain26import com.peachpasswords.ui.lock.LockScreen27import com.peachpasswords.ui.theme.ACCENT_COLORS28import com.peachpasswords.ui.theme.DEFAULT_ACCENT29import com.peachpasswords.ui.theme.PeachTheme30import com.peachpasswords.vault.VaultRepository31import dagger.hilt.android.AndroidEntryPoint32import java.time.Instant33import javax.inject.Inject3435@AndroidEntryPoint36class CredentialProviderUnlockActivity : FragmentActivity() {37 @Inject lateinit var vault: VaultRepository38 private var debugSecureLease = false3940 override fun onCreate(savedInstanceState: Bundle?) {41 super.onCreate(savedInstanceState)42 if (!BuildConfig.DEBUG) SecureWindowPolicy.setUserSecure(window, true)43 else if (!intent.getBooleanExtra(QaFlags.EXTRA_DISABLE_SECURE_CAPTURE, false)) {44 SecureWindowPolicy.acquire(window)45 debugSecureLease = true46 }47 val request = intent.getBundleExtra(PasskeyUnlockIntent.EXTRA_REQUEST)48 ?.let(BeginGetCredentialRequest::fromBundle)49 if (request == null) {50 finish()51 return52 }53 val settings = vault.settings.value54 val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT55 setContent {56 PeachTheme(accent = accent, textSize = settings.textSize) {57 LockScreen(58 onUnlocked = { complete(request) },59 onNavigate = { finish() },60 )61 }62 }63 }6465 override fun onDestroy() {66 if (debugSecureLease) SecureWindowPolicy.release(window)67 super.onDestroy()68 }6970 private fun complete(request: BeginGetCredentialRequest) {71 val info = request.callingAppInfo72 val allowlist = PasskeyCallerOrigin.privilegedAllowlist(this)73 val webOrigin = info?.let { runCatching { it.getOrigin(allowlist) }.getOrNull() }74 val domains = if (!webOrigin.isNullOrBlank()) {75 setOfNotNull(extractDomain(webOrigin))76 } else {77 info?.let { appLinkDomains(this, it.packageName).toSet() }.orEmpty()78 }79 val packageName = info?.packageName?.takeIf { webOrigin.isNullOrBlank() }80 val icon = Icon.createWithResource(this, R.drawable.ic_peach_mark)81 val callerOrigin = info?.let {82 PasskeyCallerOrigin.resolve(it, allowlist)83 }84 val entries = request.beginGetCredentialOptions.flatMap { option ->85 when (option) {86 is BeginGetPasswordOption -> PasswordCredentialPlanner.candidates(87 vault.getEntries(),88 domains,89 packageName = packageName,90 allowedUserIds = option.allowedUserIds,91 ).map { candidate ->92 PasswordCredentialEntry.Builder(93 this,94 candidate.username,95 PasswordCredentialSelectionActivity.pendingIntent(this, candidate.entryId),96 option,97 )98 .setDisplayName(candidate.title)99 .setLastUsedTime(Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L))100 .setIcon(icon)101 .setAutoSelectAllowed(false)102 .build()103 }104 is BeginGetPublicKeyCredentialOption -> PasskeyCredentialPlanner.candidates(105 vault.getEntries(),106 option.requestJson,107 callerOrigin,108 ).map { candidate ->109 PublicKeyCredentialEntry(110 context = this,111 username = candidate.username,112 pendingIntent = PasskeyCredentialSelectionActivity.pendingIntent(113 this,114 candidate.entryId,115 option.requestJson,116 ),117 beginGetPublicKeyCredentialOption = option,118 displayName = candidate.displayName,119 lastUsedTime = Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L),120 icon = icon,121 isAutoSelectAllowed = false,122 )123 }124 else -> emptyList<CredentialEntry>()125 }126 }127128 val result = Intent()129 PendingIntentHandler.setBeginGetCredentialResponse(130 result,131 BeginGetCredentialResponse.Builder().setCredentialEntries(entries).build(),132 )133 setResult(RESULT_OK, result)134 finish()135 }136}