Skip to content

feat(sample-app): integrate OpenID4VP flow and expand sample wallet #2298

Open
Kaushikgupta469 wants to merge 6 commits into
inji:developfrom
Kaushikgupta469:sample-ovp-feature
Open

feat(sample-app): integrate OpenID4VP flow and expand sample wallet #2298
Kaushikgupta469 wants to merge 6 commits into
inji:developfrom
Kaushikgupta469:sample-ovp-feature

Conversation

@Kaushikgupta469
Copy link
Copy Markdown
Collaborator

@Kaushikgupta469 Kaushikgupta469 commented Apr 14, 2026

Added OpenID4VP integration in sample app for VP flow.
Updated UI to support credential presentation along with existing issuance/verification.
Added handling for success/failure and matching credential scenario

Issue ticket number and link

Example : GITHUB-issues2120

Attaching video for reference

sample-ovp.mp4

Summary by CodeRabbit

  • New Features

    • Full credential-sharing flow: QR scanner, credential matching UI with multi-select/consent, share success screen, and OpenID4VP wallet integration.
    • Added credential metadata, matching logic, key management, and viewmodel support to back sharing.
  • Bug Fixes

    • Improved redirect/deeplink handling, more robust QR scanning/camera fallback, and clearer key-auth error messages.
    • Better handling/reporting for share failures and timeout scenarios.
  • Chores

    • Build/tooling updates and extended download timeouts.

…apabilities

Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Integrates OpenID4VP into the sample credential wallet: build/dependency updates, OpenID4VP manager, key management and signing, credential-VC bridging and matching, new VP sharing UI and ViewModel, navigation/deeplink updates, keystore policy migration, and supporting strings/logging.

Changes

Cohort / File(s) Summary
Build & Gradle
sample-application/.../app/build.gradle.kts
Enable core-library desugaring, add io.inji:inji-openid4vp-aar:0.7.0, JSON/crypto libs, and global excludes for BouncyCastle and non-jre8 Titanium JSON-LD.
App Entry & Deeplink
sample-application/.../MainActivity.kt
Init OpenID4VPManager on app start; enhanced deeplink handling to collect all query params and call both AuthCodeHolder.complete and completeV2.
Navigation & Camera
sample-application/.../AppNavHost.kt
Add VP routes (vp_share, matching_credentials, vp_success), wire OVPViewModel, update AuthWebView redirectUri fallback, adjust navigation flows, ML Kit barcode options, CameraX preview mode and image analysis resolution, and QR fallback decoding.
OVP Data & Models
.../ovp/data/OVPData.kt, .../ovp/data/VCMetadata.kt, .../ovp/utils/OVPConstants.kt
Add wallet metadata, trusted verifiers parsing, VCMetadata data class, and OVP error constants.
Credential Bridge & Matching
.../ovp/utils/CredentialStoreOVPBridge.kt, .../ovp/utils/MatchingVcsHelper.kt
Map stored credentials → VCMetadata; implement presentationDefinition matching (JSONPath, constraints, schema checks, fallbacks) and helpers to build selected VCs map.
Key Management & Signing
.../ovp/utils/OVPKeyManager.kt
Add key loading/generation (RSA/ES256/Ed25519), holder ID derivation, VP and device-auth signing flows, and algorithm helpers.
OpenID4VP Manager
.../ovp/utils/OpenID4VPManager.kt
Singleton init/authenticate/share/sendError APIs; VP construction, format-specific signing, submit/timeout handling, and error forwarding.
OVP UI & ViewModel
.../ovp/ui/VPShareScreen.kt, .../ovp/ui/MatchingCredentialsScreen.kt, .../ovp/ui/VPSuccessScreen.kt, .../ovp/viewmodel/OVPViewModel.kt
Add QR-scanner VPShare screen, matching credentials UI with consent/decline flows, success screen, and OVPViewModel for scanned QR and match state.
Auth & Credential Flows
.../ui/auth/AuthWebViewScreen.kt, .../ui/credential/CredentialDownloadScreen.kt
Use modern shouldOverrideUrlLoading, robust redirect matching and full query-param extraction, dual AuthCodeHolder completion; detect key-auth errors and annotate mdoc credentials with deviceKeyAlias.
Home & UX
.../ui/home/HomeScreen.kt, .../res/values/strings.xml
Add Share FAB + empty-list dialog, update composition effects and credential display-name handling, and add strings for VP/scanning/sharing flows.
Utilities & Keystore
.../utils/CredentialDownloadManager.kt, .../utils/IssuerConfiguration.kt, .../utils/SecureKeyStoreManager.kt
Increase download timeouts and logging, add new V2 issuer entries, and introduce key policy versioning with migration and disabled biometric gating for signing in sample app.
Credential UI/Parsing
.../ui/credential/CredentialListScreen.kt, .../utils/CredentialParser.kt
Add derived credentialName header in list UI and centralize display-name resolution and normalization logic.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant App as Wallet App
    participant Scanner as QR Scanner
    participant OVP as OpenID4VPManager
    participant Store as Credential Store
    participant Verifier as Verifier

    User->>App: Tap "Share credentials"
    App->>Scanner: Start camera & scan QR
    Scanner->>App: QR text (urlEncoded auth request)
    App->>OVP: authenticateVerifier(urlEncodedAuthRequest)
    OVP->>Verifier: Validate & parse auth request
    Verifier-->>OVP: AuthorizationRequest
    OVP-->>App: Parsed presentationDefinition
    App->>Store: getAllCredentialMetadata()
    Store-->>App: List<VCMetadata>
    App->>App: MatchingVcsHelper.getVcsMatchingAuthRequest(...)
    App-->>User: Show matching credentials UI
    User->>App: Select & confirm share
    App->>OVP: shareVerifiablePresentation(selectedItems)
    OVP->>OVP: buildSelectedVCsMapPlain() & sign VPs
    OVP->>Verifier: Submit VP payload
    Verifier-->>OVP: VerifierResponse (maybe redirect)
    OVP-->>App: onResult(VerifierResponse)
    App->>App: Handle redirect → navigate to VPSuccess
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • KiruthikaJeyashankar
  • swatigoel
  • abhip2565

Poem

🐰 I found a QR beneath the log,
I hopped and scanned through VC and fog,
I signed a token, sent with cheer,
The verifier replied — success is near,
Hooray — credentials shared with a happy jog! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and accurately summarizes the main objective: integrating OpenID4VP flow into the sample wallet application.
Description check ✅ Passed The PR description covers the core changes (OpenID4VP integration, VP flow support, credential presentation UI updates, success/failure handling) and includes an issue reference and supporting video.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/MainActivity.kt (1)

30-36: ⚠️ Potential issue | 🟠 Major

Startup still skips the new key-policy migration for upgraded installs.

The migration now lives inside SecureKeystoreManager.initializeKeystore(), but initializeKeystoreIfNeeded() still returns early whenever keys_generated is already true. With the new VP share path reachable directly from Home, an upgraded install can enter presentation flow on first launch with the old auth-gated keys and still hit KEY_USER_NOT_AUTHENTICATED. Call initializeKeystore() unconditionally here and let the manager decide whether regeneration is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/MainActivity.kt`
around lines 30 - 36, The startup path in MainActivity.kt currently
early-returns from initializeKeystoreIfNeeded() when keys_generated is true,
skipping the new key-policy migration; instead, always call
SecureKeystoreManager.initializeKeystore() on launch so the manager can decide
whether regeneration/migration is needed. Replace or modify the
initializeKeystoreIfNeeded() invocation to call
keystoreManager.initializeKeystore() unconditionally (or refactor
initializeKeystoreIfNeeded() to always invoke
SecureKeystoreManager.initializeKeystore()), ensuring the manager handles the
keys_generated check and migration logic.
🧹 Nitpick comments (2)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPConstants.kt (1)

4-5: Move user-facing error text to Android string resources.

These are UI-visible messages; keeping them in strings.xml improves localization and keeps copy centralized.

♻️ Proposed refactor
 object OVPConstants {
-    const val ERR_DECLINED = "The user has declined to share their credentials at this time."
-    const val ERR_NO_MATCHING_VCS = "No matching credentials found to fulfill the request."
+    const val ERR_DECLINED = "err_declined"
+    const val ERR_NO_MATCHING_VCS = "err_no_matching_vcs"
 }
<!-- res/values/strings.xml -->
<string name="err_declined">The user has declined to share their credentials at this time.</string>
<string name="err_no_matching_vcs">No matching credentials found to fulfill the request.</string>
// when rendering message
context.getString(R.string.err_declined)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPConstants.kt`
around lines 4 - 5, These two user-facing constants in OVPConstants.kt
(ERR_DECLINED and ERR_NO_MATCHING_VCS) should be moved into Android string
resources and references updated: add <string> entries for err_declined and
err_no_matching_vcs in res/values/strings.xml, then update any code that
currently uses OVPConstants.ERR_DECLINED or OVPConstants.ERR_NO_MATCHING_VCS to
call context.getString(R.string.err_declined) /
context.getString(R.string.err_no_matching_vcs) (or pass a Context/Resources to
the caller), and finally remove the hardcoded constants from OVPConstants.kt.
Ensure usages in UI rendering or error displays (search for ERR_DECLINED and
ERR_NO_MATCHING_VCS) are replaced accordingly.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/IssuerConfiguration.kt (1)

125-131: Consider consolidating duplicated issuer metadata across V1/V2 repositories.

StayProtected (and other issuers) is defined in both IssuerRepositoryV2 and IssuerRepository, which can drift over time. A shared source model with V1/V2 adapters would reduce maintenance risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/IssuerConfiguration.kt`
around lines 125 - 131, The duplicate issuer metadata (e.g., the "StayProtected"
IssuerConfiguration instance) exists in both IssuerRepositoryV2 and
IssuerRepository; extract the canonical issuer definitions into a single shared
source (e.g., a new IssuerRepositoryCommon or IssuerConfigurations object) that
exposes a map of issuer keys to IssuerConfiguration, then refactor
IssuerRepositoryV2 and IssuerRepository to read from that shared source or
implement thin adapter methods that transform the shared IssuerConfiguration
into the specific V1/V2 shapes expected; update references to the
IssuerConfiguration instances (the "StayProtected" entry and any others) so they
are defined only once and both repositories use the shared adapter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sample-application/sample-application/app/build.gradle.kts`:
- Line 106: Replace the SNAPSHOT dependency with the released RC artifact to
improve build reproducibility: locate the implementation(...) dependency
declaration that currently references
"io.inji:inji-openid4vp-aar:0.7.0-SNAPSHOT" in the build.gradle.kts and update
the version to "0.7.0-RC1" (i.e., change the string inside the
implementation(...) call); after updating, run your build and tests to confirm
the RC1 artifact is compatible with the project.
- Around line 59-66: The global exclusions in configurations.configureEach are
too broad and may remove transitive deps for test/aux configs; remove only the
two lines that exclude group "io.mosip", module "vcverifier-jar" and group
"com.apicatalog", module "titanium-json-ld" from the
configurations.configureEach block, leaving the org.bouncycastle and bcpkix
excludes in place, and rely on the existing dependency-level exclusions in
inji-vci-client-aar, vcverifier-aar, inji-openid4vp-aar, and pixelpass-aar to
prevent those artifacts from being brought in.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt`:
- Around line 307-317: Wrap the call to OpenID4VPManager.sendErrorToVerifier
inside a try/catch (or try/catch/finally) within the coroutineScope.launch block
so any thrown exception is caught and does not cancel the coroutine; ensure that
onDeclineConfirmed() is always invoked (e.g., call it in finally or after catch)
and still attempt to call handleVerifierResponse(verifierResponse,
navController) only if a response was obtained (or handle a null/failed response
path). Modify the coroutine that calls sendErrorToVerifier, referencing
coroutineScope.launch, OpenID4VPManager.sendErrorToVerifier,
handleVerifierResponse, and onDeclineConfirmed to guarantee navigation/decline
proceeds even when sendErrorToVerifier throws.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 118-189: The camera setup leaks resources and binds to the wrong
lifecycle; fix by using LocalLifecycleOwner.current as the lifecycle owner
(replace casts of context as LifecycleOwner), switch cameraProviderFuture.get()
to cameraProviderFuture.addListener(...) and perform bindToLifecycle inside that
listener using the lifecycleOwner, move executor creation into remember {
Executors.newSingleThreadExecutor() } and dispose it in a DisposableEffect tied
to the composable, create the BarcodeScanning.getClient(...) outside the
AndroidView factory (e.g., as a remembered val) and close it in a
DisposableEffect, and ensure any created analyzers/use-cases are released when
the DisposableEffect is disposed so bind/unbind and resource cleanup mirror the
pattern in AppNavHost.QrScannerScreen.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/CredentialStoreOVPBridge.kt`:
- Around line 36-40: toVCMetadata currently parses credentialJson into a
JsonObject and sets VCMetadata(rawCBORData = null), which drops MSO_MDOC
credentials because MatchingVcsHelper.buildSelectedVCsMapPlain expects
rawCBORData for mdoc entries; update toVCMetadata to detect when
detectFormat(vcObject) == MSO_MDOC and, if so, preserve the original CBOR
payload by setting VCMetadata.rawCBORData to the original credentialJson (or the
store-provided CBOR bytes when available) and otherwise leave it null; ensure
the change references toVCMetadata, detectFormat, VCMetadata.rawCBORData and
retains existing behavior for non-MSO_MDOC formats so mdoc credentials are not
silently omitted from VP responses.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`:
- Around line 66-82: The current logic treats lenientInclude (computed from
matchesFormat && !matchesConstraints && constraints?.has("fields") == true) as a
valid match and adds it to shouldInclude, which lets MatchingCredentialsScreen
show credentials that fail verifier constraints; change shouldInclude to only
use strictInclude (i.e., set shouldInclude = strictInclude) so failed constraint
checks are not considered matches, remove lenientInclude from the match
decision, and instead surface such interoperability candidates separately (e.g.,
create a separate flag or list like interopCandidates/interopWarning associated
with descriptorId and vcMetadata.format that the UI can display as a warning)
while keeping the existing Log.w about lenient cases for diagnostics.
- Around line 48-55: The matcher currently only checks format/constraints and
sets hasFormatOrConstraints, which ignores input_descriptor.schema; update
MatchingVcsHelper (the matching routine that reads inputDescriptor, format,
constraints, descriptorId and uses hasFormatOrConstraints) to also evaluate
inputDescriptor.get("schema")/getAsJsonArray("schema") and apply schema-aware
filtering against stored credential types/schema URIs (e.g., match
credential.type or credential.schema.id to the descriptor schema entries); if
schema matches are absent treat the descriptor as restrictive (fail closed)
instead of using the "attach all credentials" fallback—i.e., incorporate schema
checks into the same branch that sets hasFormatOrConstraints and into the
credential-filtering logic currently used where fallback occurs.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt`:
- Around line 42-51: The shareVerifiablePresentation function currently launches
a detached CoroutineScope(Dispatchers.IO).launch which causes onResult/onError
to be invoked on a background thread and can break Compose state updates (e.g.,
isSharing, shareErrorMessage); update shareVerifiablePresentation to either be
declared suspend and remove the internal CoroutineScope launch so callers
control the scope, or keep it non-suspending but ensure you switch back to
Dispatchers.Main (e.g., withContext(Dispatchers.Main)) before calling
onResult/onError after sendVP (note sendVP already uses Dispatchers.IO), and
reference shareVerifiablePresentation, sendVP, onResult, onError, isSharing and
shareErrorMessage when making the change.
- Around line 116-118: OpenID4VPManager currently calls
OVPKeyManager.generateKeyPair(mdocKeyType) for MSO_MDOC device authentication
which breaks device-bound mDOC verification; instead persist and reuse the
device key created at issuance: extend the credential metadata to store a device
key reference (alias or persistent id) when issuing the credential, then in the
verification/signing path (where mdocKeyType and mdocKeyPair are set) look up
that alias in your keystore via OVPKeyManager (e.g., a new
OVPKeyManager.getKeyPairByAlias(alias) or equivalent) and use the retrieved
public/private key for DeviceAuthentication signing; remove the
generateKeyPair() call for MSO_MDOC and add null/exists checks and clear error
logging if the referenced key is missing so verification fails cleanly.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt`:
- Around line 92-100: The JWS header algorithm is hardcoded to ES256 which
mismatches the RSASSASigner when keyType == OVPKeyType.RSA; update the header
creation in signDeviceAuthentication (in OVPKeyManager) to choose JWSAlgorithm
based on keyType (e.g., ES256 for OVPKeyType.ES256, RS256 for OVPKeyType.RSA)
and keep the signer selection as-is, and ensure any returned signatureAlgorithm
or metadata uses the same chosen algorithm so alg and signer remain consistent.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/viewmodel/OVPViewModel.kt`:
- Around line 24-33: In storeMatchResult and clearMatchResult, remove the
unnecessary viewModelScope.launch wrappers and set _matchingResult.value
synchronously (e.g., _matchingResult.value = result and _matchingResult.value =
null) because MutableStateFlow.value is thread-safe; then remove any
compensating delays or workarounds (such as delay(100) in VPShareScreen) that
were added to mask the race so that navigation reads the updated state
immediately.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/auth/AuthWebViewScreen.kt`:
- Around line 101-110: The handleRedirect function currently uses
startsWith(redirectUri) which accepts prefix lookalikes and treats an empty
redirectUri as matching everything; fix it by rejecting blank/empty redirectUri
early and by parsing both the configured redirectUri and the incoming url with
Uri.parse and comparing their components (scheme, authority/host+port, and path
or pathSegments) for equality instead of prefix matching; update handleRedirect
to return false if redirectUri is null/blank, parse val expected =
Uri.parse(redirectUri) and val actual = Uri.parse(url), then only proceed when
expected.scheme == actual.scheme && expected.authority == actual.authority &&
expected.path == actual.path (or equivalent pathSegments comparison) before
treating it as a redirect.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/SecureKeyStoreManager.kt`:
- Around line 74-83: The migration currently swallows exceptions from
keystore.removeAllKeys() and proceeds to regenerate keys and
markKeysAsGenerated(), which can incorrectly treat migration as successful;
change the flow in the needsKeyPolicyMigration() block so that when
keystore.removeAllKeys() throws you abort the migration (either rethrow the
exception or return/exit the migration function) and do not set
prefs.edit().putBoolean(KEY_KEYS_GENERATED, true)/call markKeysAsGenerated() or
run generateKeyPair*()—ensure the failure is logged and propagated so callers
can retry migration instead of stamping policy version 2.

---

Outside diff comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/MainActivity.kt`:
- Around line 30-36: The startup path in MainActivity.kt currently early-returns
from initializeKeystoreIfNeeded() when keys_generated is true, skipping the new
key-policy migration; instead, always call
SecureKeystoreManager.initializeKeystore() on launch so the manager can decide
whether regeneration/migration is needed. Replace or modify the
initializeKeystoreIfNeeded() invocation to call
keystoreManager.initializeKeystore() unconditionally (or refactor
initializeKeystoreIfNeeded() to always invoke
SecureKeystoreManager.initializeKeystore()), ensuring the manager handles the
keys_generated check and migration logic.

---

Nitpick comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPConstants.kt`:
- Around line 4-5: These two user-facing constants in OVPConstants.kt
(ERR_DECLINED and ERR_NO_MATCHING_VCS) should be moved into Android string
resources and references updated: add <string> entries for err_declined and
err_no_matching_vcs in res/values/strings.xml, then update any code that
currently uses OVPConstants.ERR_DECLINED or OVPConstants.ERR_NO_MATCHING_VCS to
call context.getString(R.string.err_declined) /
context.getString(R.string.err_no_matching_vcs) (or pass a Context/Resources to
the caller), and finally remove the hardcoded constants from OVPConstants.kt.
Ensure usages in UI rendering or error displays (search for ERR_DECLINED and
ERR_NO_MATCHING_VCS) are replaced accordingly.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/IssuerConfiguration.kt`:
- Around line 125-131: The duplicate issuer metadata (e.g., the "StayProtected"
IssuerConfiguration instance) exists in both IssuerRepositoryV2 and
IssuerRepository; extract the canonical issuer definitions into a single shared
source (e.g., a new IssuerRepositoryCommon or IssuerConfigurations object) that
exposes a map of issuer keys to IssuerConfiguration, then refactor
IssuerRepositoryV2 and IssuerRepository to read from that shared source or
implement thin adapter methods that transform the shared IssuerConfiguration
into the specific V1/V2 shapes expected; update references to the
IssuerConfiguration instances (the "StayProtected" entry and any others) so they
are defined only once and both repositories use the shared adapter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38c0b4cf-e9f9-4be6-83e0-ecb36f5f6666

📥 Commits

Reviewing files that changed from the base of the PR and between bc1a834 and e4bdef4.

📒 Files selected for processing (21)
  • sample-application/sample-application/app/build.gradle.kts
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/MainActivity.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/navigation/AppNavHost.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/data/OVPData.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/data/VCMetadata.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPSuccessScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/CredentialStoreOVPBridge.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPConstants.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/viewmodel/OVPViewModel.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/auth/AuthWebViewScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialDownloadScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialDownloadManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/IssuerConfiguration.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/SecureKeyStoreManager.kt
  • sample-application/sample-application/app/src/main/res/values/strings.xml

Comment thread sample-application/sample-application/app/build.gradle.kts
Comment thread sample-application/sample-application/app/build.gradle.kts Outdated
Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt (1)

330-376: ⚠️ Potential issue | 🟡 Minor

Unreachable false branch in when expression.

The when block at lines 330-376 has true, false -> as the first branch (line 331), which matches both true and false. The subsequent false -> branch (line 346) is unreachable dead code and will never execute.

🐛 Proposed fix
                     when (isValid) {
-                        true, false -> {
+                        true -> {
                             Icon(
                                 imageVector = Icons.Default.CheckCircle,
                                 contentDescription = "Verified",
                                 tint = Color(0xFF4CAF50),
                                 modifier = Modifier.size(14.dp)
                             )
                             Spacer(modifier = Modifier.width(4.dp))
                             Text(
                                 text = "Valid",
                                 style = MaterialTheme.typography.bodySmall,
                                 color = Color.White,
                                 fontSize = 11.sp
                             )
                         }
                         false -> {
                             Icon(
                                 imageVector = Icons.Default.Info,
                                 contentDescription = "Not Verified",
                                 tint = Color(0xFFFF9800),
                                 modifier = Modifier.size(14.dp)
                             )
                             Spacer(modifier = Modifier.width(4.dp))
                             Text(
                                 text = "Unverified",
                                 style = MaterialTheme.typography.bodySmall,
                                 color = Color.White,
                                 fontSize = 11.sp
                             )
                         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt`
around lines 330 - 376, The when expression handling isValid in HomeScreen.kt
incorrectly uses "true, false" in the first branch, making the later "false"
branch unreachable; update the when on isValid (in the HomeScreen composable) so
each branch is distinct—use "true ->" for the Valid UI, "false ->" for the
Unverified UI, and "null ->" for the Checking... UI (remove the combined "true,
false" pattern).
🧹 Nitpick comments (3)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt (1)

146-153: Fallback assigns all VCs when no format/constraints/schema are specified.

When hasFormatOrConstraints is false (meaning no descriptor has format, constraint fields, or schema), all credentials are assigned to the first descriptor (lines 149-151). While this may be intentional for permissive verifier requests, it could inadvertently share unrelated credentials.

Consider logging a warning or documenting this behavior explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`
around lines 146 - 153, The current fallback branch in MatchingVcsHelper (when
hasFormatOrConstraints is false) assigns all credentials from vcList to the
first input descriptor (using inputDescriptors[0] and fallbackId) which can
unintentionally expose unrelated VCs; update the code in the block that builds
matchingVCs[fallbackId] to (1) add a clear warning log via your logger (or
processLogger) mentioning fallback assignment of all VCs to the first descriptor
(include fallbackId and vcList.size()), and (2) add an inline comment
documenting this permissive behavior so future maintainers notice the
security/privacy implications; keep the existing assignment logic unless an
explicit narrowing is required.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt (1)

263-289: Extract hardcoded dialog strings to string resources.

The "Sharing in progress" (line 266), "Please wait while we submit your VP response" (line 271), and "Share failed" (line 281) strings should use stringResource(R.string.*) for consistency with the rest of the UI and to support localization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt`
around lines 263 - 289, In MatchingCredentialsScreen replace the hardcoded
dialog strings with string resources: change the title "Sharing in progress" to
stringResource(R.string.sharing_in_progress), the body "Please wait while we
submit your VP response" to
stringResource(R.string.sharing_in_progress_message), and the error title "Share
failed" to stringResource(R.string.share_failed); add those keys to strings.xml
(sharing_in_progress, sharing_in_progress_message, share_failed) and ensure the
file imports stringResource (androidx.compose.ui.res.stringResource) so the
AlertDialog title/text Text() calls use stringResource(...) instead of literal
strings.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt (1)

59-77: Consider narrowing the return type or documenting the mixed-type contract.

generateKeyPair returns Any, which requires callers to cast based on keyType. This is acceptable for internal use, but adding a KDoc comment clarifying that RSA/ES256 return KeyPair while Ed25519 returns OctetKeyPair would improve maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt`
around lines 59 - 77, generateKeyPair currently returns Any forcing callers to
cast; replace this with a clear typed result by introducing a sealed class
(e.g., GeneratedKey) with variants for RSA/ES256 that carry a
java.security.KeyPair (e.g., GeneratedKey.KeyPairResult(val keyPair: KeyPair))
and for Ed25519 that carries the OctetKeyPair (e.g.,
GeneratedKey.OctetKeyPairResult(val key: OctetKeyPair)), update
generateKeyPair(keyType: OVPKeyType): GeneratedKey to return the appropriate
variant, and add KDoc on the generateKeyPair function and the GeneratedKey
sealed class documenting which variant maps to which OVPKeyType (reference
symbols: generateKeyPair, OVPKeyType, KeyPair, OctetKeyPair).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 219-233: The LaunchedEffect(Unit) inside the if (showErrorDialog)
block (in VPShareScreen) can run multiple times across recompositions; add a
dedicated boolean state (e.g., errorSent or hasSentVerifierError) private to the
composable and set it to true after sending, or move the send logic out of the
UI conditional so you call OpenID4VPManager.sendErrorToVerifier only once;
update the code paths that reference showErrorDialog and LaunchedEffect(Unit) to
check/set this flag (or invoke send logic once) so that
OpenID4VPManager.sendErrorToVerifier /
OpenID4VPExceptions.InvalidTransactionData is not called repeatedly.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt`:
- Around line 194-206: The code currently keeps the first device-key alias seen
for a given mdoc docType in the selectedItems loop (in OpenID4VPManager.kt) and
silently ignores later conflicting aliases; change the logic in the processing
loop that uses extractMdocDocType and aliasesByDocType so that when a docType is
already present and the new metadata.deviceKeyAlias (trimmed) is non-blank and
differs from the stored alias, you reject the selection by throwing or returning
an explicit error (e.g., IllegalStateException or a Domain-specific error)
indicating a conflicting device-key alias for that docType; ensure the check
handles null/blank aliases correctly (ignore blank new aliases) and includes the
docType and both aliases in the error message for debugging.
- Around line 42-51: The code reads a live SnapshotStateList on a background
thread; before launching the IO coroutine in shareVerifiablePresentation, create
an immutable snapshot (e.g., val items = selectedItems.toList()) and use that
copy inside the coroutine and when calling sendVP; update sendVP's signature
from accepting SnapshotStateList<Pair<String, VCMetadata>> to List<Pair<String,
VCMetadata>> and adjust its implementation and any call sites (including the
other call path currently passing selectedItems) to accept the immutable List so
no SnapshotStateList is accessed off the main snapshot context.

---

Outside diff comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt`:
- Around line 330-376: The when expression handling isValid in HomeScreen.kt
incorrectly uses "true, false" in the first branch, making the later "false"
branch unreachable; update the when on isValid (in the HomeScreen composable) so
each branch is distinct—use "true ->" for the Valid UI, "false ->" for the
Unverified UI, and "null ->" for the Checking... UI (remove the combined "true,
false" pattern).

---

Nitpick comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt`:
- Around line 263-289: In MatchingCredentialsScreen replace the hardcoded dialog
strings with string resources: change the title "Sharing in progress" to
stringResource(R.string.sharing_in_progress), the body "Please wait while we
submit your VP response" to
stringResource(R.string.sharing_in_progress_message), and the error title "Share
failed" to stringResource(R.string.share_failed); add those keys to strings.xml
(sharing_in_progress, sharing_in_progress_message, share_failed) and ensure the
file imports stringResource (androidx.compose.ui.res.stringResource) so the
AlertDialog title/text Text() calls use stringResource(...) instead of literal
strings.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`:
- Around line 146-153: The current fallback branch in MatchingVcsHelper (when
hasFormatOrConstraints is false) assigns all credentials from vcList to the
first input descriptor (using inputDescriptors[0] and fallbackId) which can
unintentionally expose unrelated VCs; update the code in the block that builds
matchingVCs[fallbackId] to (1) add a clear warning log via your logger (or
processLogger) mentioning fallback assignment of all VCs to the first descriptor
(include fallbackId and vcList.size()), and (2) add an inline comment
documenting this permissive behavior so future maintainers notice the
security/privacy implications; keep the existing assignment logic unless an
explicit narrowing is required.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt`:
- Around line 59-77: generateKeyPair currently returns Any forcing callers to
cast; replace this with a clear typed result by introducing a sealed class
(e.g., GeneratedKey) with variants for RSA/ES256 that carry a
java.security.KeyPair (e.g., GeneratedKey.KeyPairResult(val keyPair: KeyPair))
and for Ed25519 that carries the OctetKeyPair (e.g.,
GeneratedKey.OctetKeyPairResult(val key: OctetKeyPair)), update
generateKeyPair(keyType: OVPKeyType): GeneratedKey to return the appropriate
variant, and add KDoc on the generateKeyPair function and the GeneratedKey
sealed class documenting which variant maps to which OVPKeyType (reference
symbols: generateKeyPair, OVPKeyType, KeyPair, OctetKeyPair).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0abea1a2-7bea-49fa-82be-3f490ed06136

📥 Commits

Reviewing files that changed from the base of the PR and between e4bdef4 and cf28b4e.

📒 Files selected for processing (16)
  • sample-application/sample-application/app/build.gradle.kts
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/navigation/AppNavHost.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/data/VCMetadata.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPSuccessScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/CredentialStoreOVPBridge.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OVPKeyManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/viewmodel/OVPViewModel.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/auth/AuthWebViewScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialDownloadScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialDownloadManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/SecureKeyStoreManager.kt
✅ Files skipped from review due to trivial changes (1)
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPSuccessScreen.kt
🚧 Files skipped from review as they are similar to previous changes (6)
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/data/VCMetadata.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialDownloadScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/auth/AuthWebViewScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialDownloadManager.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/viewmodel/OVPViewModel.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/CredentialStoreOVPBridge.kt

Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt (1)

41-72: Prefer caller-owned coroutine scope for VP sharing.

CoroutineScope(Dispatchers.IO).launch creates unmanaged work that can outlive the screen passing onResult/onError, so navigation away cannot cancel the 45s operation. Consider accepting a caller/lifecycle scope or returning the launched Job so the UI can cancel it on disposal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt`
around lines 41 - 72, The shareVerifiablePresentation function currently creates
an internal CoroutineScope(Dispatchers.IO).launch which produces unmanaged work;
change its signature to either accept a caller-provided CoroutineScope (e.g.,
add parameter scope: CoroutineScope) or return the launched Job so callers can
cancel it; inside, replace CoroutineScope(Dispatchers.IO).launch with
scope.launch(Dispatchers.IO) (or assign and return the Job from that launch),
keeping the withTimeout/withContext logic unchanged and ensuring callers
(UI/lifecycleScope) can cancel the operation when the screen is destroyed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 147-151: When scanningEnabled is toggled off the code calls
analysisUseCase?.clearAnalyzer() and boundCameraProvider?.unbindAll(), but
there's no rebind when scanningEnabled becomes true so the camera never resumes;
update the LaunchedEffect(scanningEnabled) block to handle both states: on false
keep the camera bound or clear only the analyzer (remove
boundCameraProvider?.unbindAll()), or if you prefer unbinding keep it but add a
rebind path when scanningEnabled is true that re-attaches the analyzer and
re-binds use cases via the same setup used in the AndroidView factory (mirror
the bind logic used when the preview/Analyzer is initially created), ensuring
analysisUseCase and boundCameraProvider are restored so QR scanning can resume.

---

Nitpick comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt`:
- Around line 41-72: The shareVerifiablePresentation function currently creates
an internal CoroutineScope(Dispatchers.IO).launch which produces unmanaged work;
change its signature to either accept a caller-provided CoroutineScope (e.g.,
add parameter scope: CoroutineScope) or return the launched Job so callers can
cancel it; inside, replace CoroutineScope(Dispatchers.IO).launch with
scope.launch(Dispatchers.IO) (or assign and return the Job from that launch),
keeping the withTimeout/withContext logic unchanged and ensuring callers
(UI/lifecycleScope) can cancel the operation when the screen is destroyed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2984a4b-fa40-4d73-a6b6-17bb94c9baf3

📥 Commits

Reviewing files that changed from the base of the PR and between cf28b4e and cb83ced.

📒 Files selected for processing (2)
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/OpenID4VPManager.kt

@Kaushikgupta469 Kaushikgupta469 force-pushed the sample-ovp-feature branch 3 times, most recently from ef54877 to cb83ced Compare April 21, 2026 05:30
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…eature

Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt (1)

519-523: ⚠️ Potential issue | 🟠 Major

Don’t allow failed non-type constraints through the fallback.

When constraints target claims other than type, Line 523 currently allows any format-matched VC even though full constraint evaluation already failed. That can disclose credentials that do not satisfy the verifier request.

Suggested fix
         if (!hasTypeConstraint) {
-            // No type constraint in the fields — the constraint fields are about
-            // other claims. Since format already matches, allow it through.
-            Log.d("OVP_MATCH", "typeAwareFallback: no type constraint in fields, allowing format-matched VC")
-            return true
+            Log.d("OVP_MATCH", "typeAwareFallback: no type constraint in fields, failing closed")
+            return false
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`
around lines 519 - 523, The fallback in typeAwareFallback currently returns true
whenever hasTypeConstraint is false, even if full constraint evaluation failed
for non-type claims; change the logic so you only allow format-matched VCs
through when hasTypeConstraint is false AND there were no failing non-type
constraint checks. Concretely, in the typeAwareFallback function (and where the
full constraint evaluation result is computed), replace the unconditional return
true at the hasTypeConstraint check with a guard that checks the prior
constraint-evaluation result (e.g., a boolean like nonTypeConstraintsPassed or
constraintEvaluationFailed) and only return true when that indicates no failed
non-type constraints; otherwise return false.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt (1)

169-242: ⚠️ Potential issue | 🟠 Major

Size the camera preview explicitly.

AndroidView has no modifier here, so the preview may not occupy the scanner screen reliably. Match the existing QR scanner pattern and make it fill the container.

Suggested fix
-        AndroidView(factory = { ctx ->
+        AndroidView(
+            modifier = Modifier.fillMaxSize(),
+            factory = { ctx ->
             val previewView = PreviewView(ctx)
             previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
@@
 
             previewView
-        })
+        })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`
around lines 169 - 242, The AndroidView created in VPShareScreen does not have a
Modifier so the camera preview may not fill the scanner area; update the
AndroidView invocation to include a layout modifier (e.g.,
Modifier.fillMaxSize()) and ensure the created PreviewView is sized to match its
parent by setting its layout params to match_parent (or equivalent) when you
construct previewView inside the AndroidView factory; this ensures PreviewView
(the previewView variable used with Preview.Builder and setSurfaceProvider)
reliably fills the container when binding cameraProvider in the
cameraProviderFuture listener.
🧹 Nitpick comments (2)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt (2)

49-54: Type selection here picks blindly by last index; use the preferred-type helper.

types.getString(types.length() - 1) can return "VerifiableCredential" when the array ordering puts it last (not uncommon for mdoc/JSON-LD variants), which then renders as the generic "Verifiable Credential" label even though a more specific type is available. The resolver already exposes extractPreferredType for exactly this case — using it keeps parseCredential["type"] consistent with the display-name logic that resolveFromJson uses.

Note this method is private in CredentialDisplayNameResolver; you'd need to expose it (or add a thin public wrapper) to reuse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`
around lines 49 - 54, The current type selection in CredentialParser (using
types.getString(types.length() - 1) to pick rawType and passing it to
addField("type", CredentialDisplayNameResolver.toDisplayName(rawType))) can
return the generic "VerifiableCredential"; replace this blind-last-index logic
with the resolver's preferred-type helper: call
CredentialDisplayNameResolver.extractPreferredType(...) (or add a thin public
wrapper around that private method) to obtain the preferred type from the
JSONArray/JSONObject and then pass that result into toDisplayName before calling
addField("type"). Ensure you update any visibility (make extractPreferredType
public or add a small public wrapper) so CredentialParser can call it and remove
the getString(...last) usage.

205-249: Four extractPreferredType overloads duplicate the same logic across JSONArray and JsonArray.

The two "skip VerifiableCredential, otherwise return last seen" implementations are identical save for the iteration API. Consider collapsing to a single generic helper that takes a Sequence<String> (or List<String>) of candidate type strings, and have the JSONArray/JsonArray entry points only handle extraction-to-strings. Reduces the maintenance surface if the preference rule ever changes (e.g., adding more "generic" types to skip).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`
around lines 205 - 249, The four extractPreferredType overloads duplicate the
same selection logic; refactor by creating a single helper function (e.g.,
extractPreferredTypeFromCandidates or extractPreferredType(sequence:
Sequence<String>)) that implements the "skip 'VerifiableCredential' and
otherwise return last seen" rule from a Sequence/List of Strings, then change
the two JSONArray and JsonArray entrypoints (the extractPreferredType(vararg
typeArrays: JSONArray?), extractPreferredType(typeArray: JSONArray?),
extractPreferredType(vararg typeArrays: JsonArray?),
extractPreferredType(typeArray: JsonArray?)) to each convert their elements to a
Sequence/List<String> and delegate to that helper; keep the public signatures
the same and ensure trimming/empty checks happen before delegation so behavior
is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt`:
- Around line 195-219: The click handler can leave isSharing true if
OpenID4VPManager.shareVerifiablePresentation throws synchronously or if
handleVerifierResponse throws inside the onResult coroutine; wrap the call to
OpenID4VPManager.shareVerifiablePresentation in a try/catch that sets isSharing
= false, logs the exception and updates shareErrorMessage, and inside the
onResult coroutineScope.launch wrap handleVerifierResponse(...) in its own
try/catch that also sets isSharing = false, logs the error, updates
shareErrorMessage and only clears selectedItems and call onSuccess() on success;
reference TextButton click handler, isSharing,
OpenID4VPManager.shareVerifiablePresentation, coroutineScope.launch,
handleVerifierResponse, selectedItems, shareErrorMessage and onSuccess when
applying the change.
- Around line 111-172: The share button currently enables whenever selectedItems
is non-empty; change this to require coverage of all requested input descriptors
by ensuring at least one selected VC per descriptor key: compute
coveredDescriptors = selectedItems.map { it.first }.toSet() and require
coveredDescriptors.size == matchResult!!.matchingVCs.size (or another
descriptor-count field if present) before enabling the Button and before setting
showConsentDialog in the Button onClick; update the Button's enabled check and
add a final validation before opening showConsentDialog (using selectedItems and
matchResult.matchingVCs) so sharing is blocked unless every descriptor has at
least one selection.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 333-355: The code currently uses hasMatchingVCs (any non-empty
match) before navigating; instead require a match for every requested input
descriptor: compute expectedDescriptorCount from the parsed presentation
definition (pd/inputDescriptors) and ensure matchingVcsResult.matchingVCs
contains an entry for each descriptor and that each list is non-empty (or
equivalently matchedDescriptorCount == expectedDescriptorCount and no value
lists are empty) before proceeding; update the condition that currently uses
hasMatchingVCs (and related logs) to this stricter check, keep storing the
result via ovpViewModel.storeMatchResult(matchingVcsResult), and log a clear
message when the check fails so the flow does not continue to consent with
partial matches (see MatchingVcsHelper.getVcsMatchingAuthRequest and
matchingVcsResult.matchingVCs).

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`:
- Around line 622-668: evaluateFilter currently ignores unknown JSON Schema
keywords which widens matches; add a "fail-closed" guard: collect
filter.keySet() and compare against an allowedKeys set (e.g., "type", "pattern",
"const", "enum", "contains"); if any key is not in allowedKeys return false
immediately. Also apply the same guard recursively for the JsonObject returned
by filter.getAsJsonObject("contains") (or call the same validation before using
evaluatePrimitiveMatch) so nested contains filters with unsupported keywords
also cause failure. Use the existing function name evaluateFilter and the
containsFilter variable to locate where to insert this check.
- Around line 280-287: The current matching logic in MatchingVcsHelper uses
extractSchemaTokens and stringsEquivalent to consider any shared token as a
schema match, which allows generic URI tokens (e.g., "http", "https", "www",
"schema", "vc") to cause false positives; update the matching to skip generic
tokens by adding an isGenericSchemaToken helper (as suggested) and apply it when
iterating descriptorTokens and credentialTokens so that both descriptorToken and
credentialToken are ignored if isGenericSchemaToken(token) returns true before
calling stringsEquivalent, ensuring only meaningful schema tokens drive matches.
- Around line 414-418: The current check in MatchingVcsHelper.kt reads proofType
from vc and returns true when it's null/blank, which accepts format-only
matches; instead, when the verifier requested a proof_type constraint, change
this branch to return false (and log that proof.type is missing) so missing
proof.type fails proof-type matching; locate the block that reads val proofType
= vc.getAsJsonObject("proof")?.get("type")?.asString and replace the
early-accept behavior with an early-fail when a proof_type was requested by the
verifier (use the existing verifier/requested constraints context in the same
method to decide), and update the log message accordingly.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`:
- Around line 133-139: resolveFromJson currently calls
optString("credentialName") which returns the JSON text for localized-object
values and causes false-positive matches; update resolveFromJson to detect when
credentialName is a JSONObject (check credentialJson.opt("credentialName") is
JSONObject) and, instead of using optString directly, call or reuse
extractCredentialNameFromSubject to parse both plain strings and
localized-object shapes; do the same for the nested vc path (inspect
credentialJson.optJSONObject("vc") and its credentialName field) so localized
credentialName objects don't short-circuit the fallback logic.

---

Duplicate comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 169-242: The AndroidView created in VPShareScreen does not have a
Modifier so the camera preview may not fill the scanner area; update the
AndroidView invocation to include a layout modifier (e.g.,
Modifier.fillMaxSize()) and ensure the created PreviewView is sized to match its
parent by setting its layout params to match_parent (or equivalent) when you
construct previewView inside the AndroidView factory; this ensures PreviewView
(the previewView variable used with Preview.Builder and setSurfaceProvider)
reliably fills the container when binding cameraProvider in the
cameraProviderFuture listener.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`:
- Around line 519-523: The fallback in typeAwareFallback currently returns true
whenever hasTypeConstraint is false, even if full constraint evaluation failed
for non-type claims; change the logic so you only allow format-matched VCs
through when hasTypeConstraint is false AND there were no failing non-type
constraint checks. Concretely, in the typeAwareFallback function (and where the
full constraint evaluation result is computed), replace the unconditional return
true at the hasTypeConstraint check with a guard that checks the prior
constraint-evaluation result (e.g., a boolean like nonTypeConstraintsPassed or
constraintEvaluationFailed) and only return true when that indicates no failed
non-type constraints; otherwise return false.

---

Nitpick comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`:
- Around line 49-54: The current type selection in CredentialParser (using
types.getString(types.length() - 1) to pick rawType and passing it to
addField("type", CredentialDisplayNameResolver.toDisplayName(rawType))) can
return the generic "VerifiableCredential"; replace this blind-last-index logic
with the resolver's preferred-type helper: call
CredentialDisplayNameResolver.extractPreferredType(...) (or add a thin public
wrapper around that private method) to obtain the preferred type from the
JSONArray/JSONObject and then pass that result into toDisplayName before calling
addField("type"). Ensure you update any visibility (make extractPreferredType
public or add a small public wrapper) so CredentialParser can call it and remove
the getString(...last) usage.
- Around line 205-249: The four extractPreferredType overloads duplicate the
same selection logic; refactor by creating a single helper function (e.g.,
extractPreferredTypeFromCandidates or extractPreferredType(sequence:
Sequence<String>)) that implements the "skip 'VerifiableCredential' and
otherwise return last seen" rule from a Sequence/List of Strings, then change
the two JSONArray and JsonArray entrypoints (the extractPreferredType(vararg
typeArrays: JSONArray?), extractPreferredType(typeArray: JSONArray?),
extractPreferredType(vararg typeArrays: JsonArray?),
extractPreferredType(typeArray: JsonArray?)) to each convert their elements to a
Sequence/List<String> and delegate to that helper; keep the public signatures
the same and ensure trimming/empty checks happen before delegation so behavior
is preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ddb458-9e47-4757-a7f2-0a4d75c4aae4

📥 Commits

Reviewing files that changed from the base of the PR and between cb83ced and dea74ba.

📒 Files selected for processing (9)
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/navigation/AppNavHost.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialDownloadScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialListScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt
  • sample-application/sample-application/app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (1)
  • sample-application/sample-application/app/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/home/HomeScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ui/credential/CredentialDownloadScreen.kt

Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
Comment thread sample-application/sample-application/app/build.gradle.kts Outdated
Signed-off-by: Kaushik Gupta <146950347+Kaushikgupta469@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt (1)

158-164: ⚠️ Potential issue | 🟡 Minor

Handle localized credentialName objects in the Gson overload too.

The JSONObject overload now supports credentialName: { "value": ... }, but this JsonObject overload still only reads primitive strings, so the same credential parsed through Gson falls back to type/docType instead of using the localized display name.

🛠️ Proposed fix
 fun resolveFromJson(vcObject: JsonObject): String? {
-    val direct = vcObject.getAsSafeString("credentialName")
+    val direct = extractCredentialNameFromSubject(vcObject)
     if (!direct.isNullOrBlank()) return direct
 
     val nestedVc = vcObject.getAsSafeObject("vc")
-    val nestedDirect = nestedVc?.getAsSafeString("credentialName")
+    val nestedDirect = extractCredentialNameFromSubject(nestedVc)
     if (!nestedDirect.isNullOrBlank()) return nestedDirect
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`
around lines 158 - 164, In resolveFromJson, extend the JsonObject handling to
accept localized credentialName objects (e.g., credentialName: { "value": "..."
}) as well as primitive strings: after reading
vcObject.getAsSafeString("credentialName") and
vcObject.getAsSafeObject("credentialName"), check if the object exists and
extract its "value" string (falling back to nested vc handling), and similarly
for the nestedVc variable; update the resolution order in resolveFromJson to
return the extracted "value" when present before falling back to type/docType.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt (2)

314-318: ⚠️ Potential issue | 🟠 Major

Treat credential URI path tokens as generic schema tokens.

Schema URLs that only share path components like credential/credentials can still match unrelated credential types. Add those to the generic-token denylist so schema matching is driven by meaningful type-specific tokens.

Suggested tightening
     private fun isGenericSchemaToken(token: String): Boolean {
         return when (token.trim().lowercase()) {
-            "http", "https", "www", "schema", "schemas", "vc" -> true
+            "http", "https", "www", "schema", "schemas", "credential", "credentials", "vc" -> true
             else -> false
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`
around lines 314 - 318, The isGenericSchemaToken function currently treats
common host/protocol tokens as generic; extend its denylist to also include path
tokens like "credential" and "credentials" so that schema matching ignores those
generic path components; update the when-clause in isGenericSchemaToken
(referenced by function name) to include "credential" and "credentials"
alongside the existing "http", "https", "www", "schema", "schemas", "vc".

537-541: ⚠️ Potential issue | 🟠 Major

Do not let failed non-type constraints pass via fallback.

When constraint fields do not target type, this returns true, so a format/schema-matched VC that failed required claim constraints can still be presented. Make this fail closed unless the fallback can verify the specific failed constraint.

Suggested tightening
         if (!hasTypeConstraint) {
             // No type constraint in the fields — the constraint fields are about
-            // other claims. Since format already matches, allow it through.
-            Log.d("OVP_MATCH", "typeAwareFallback: no type constraint in fields, allowing format-matched VC")
-            return true
+            // other claims. Do not override a failed constraint match.
+            Log.d("OVP_MATCH", "typeAwareFallback: no type constraint in fields; failing closed")
+            return false
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`
around lines 537 - 541, The typeAwareFallback logic in MatchingVcsHelper.kt
currently returns true when hasTypeConstraint is false, which allows VCs that
failed non-type claim constraints to slip through; update the typeAwareFallback
method to fail closed by returning false when hasTypeConstraint is false (or
only return true if the fallback explicitly verifies the specific failed
non-type constraint), and ensure the change references the hasTypeConstraint
boolean and the typeAwareFallback function so callers of type/schema matching
cannot bypass failed claim constraints.
sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt (1)

244-263: ⚠️ Potential issue | 🟠 Major

Mark the verifier-error send as attempted before the IO call.

If sendErrorToVerifier(...) throws, hasSentVerifierError stays false, so recomposition while the overlay is visible can retry the external call repeatedly. Set the guard on the main coroutine before switching to Dispatchers.IO.

Suggested fix
             LaunchedEffect(showErrorDialog, hasSentVerifierError) {
                 if (!showErrorDialog || hasSentVerifierError) {
                     return@LaunchedEffect
                 }
 
+                hasSentVerifierError = true
                 withContext(Dispatchers.IO) {
                     try {
                         OpenID4VPManager.sendErrorToVerifier(
                             OpenID4VPExceptions.InvalidTransactionData(
                                 OVPConstants.ERR_NO_MATCHING_VCS,
@@
                                 "VPShareScreen"
                             )
                         )
-                        hasSentVerifierError = true
                     } catch (e: Exception) {
                         Log.e("VPShareScreen", "Failed to send error to verifier", e)
                     }
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`
around lines 244 - 263, The current LaunchedEffect may retry sending verifier
errors if OpenID4VPManager.sendErrorToVerifier(...) throws because
hasSentVerifierError is only set after the IO call; move the guard update to
before the IO switch: inside the LaunchedEffect (guarded by showErrorDialog and
!hasSentVerifierError) set hasSentVerifierError = true on the main coroutine,
then call withContext(Dispatchers.IO) to invoke
OpenID4VPManager.sendErrorToVerifier(...) and handle/log any exception without
resetting hasSentVerifierError; reference showErrorDialog, hasSentVerifierError,
LaunchedEffect, and OpenID4VPManager.sendErrorToVerifier when locating the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt`:
- Around line 244-263: The current LaunchedEffect may retry sending verifier
errors if OpenID4VPManager.sendErrorToVerifier(...) throws because
hasSentVerifierError is only set after the IO call; move the guard update to
before the IO switch: inside the LaunchedEffect (guarded by showErrorDialog and
!hasSentVerifierError) set hasSentVerifierError = true on the main coroutine,
then call withContext(Dispatchers.IO) to invoke
OpenID4VPManager.sendErrorToVerifier(...) and handle/log any exception without
resetting hasSentVerifierError; reference showErrorDialog, hasSentVerifierError,
LaunchedEffect, and OpenID4VPManager.sendErrorToVerifier when locating the
change.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt`:
- Around line 314-318: The isGenericSchemaToken function currently treats common
host/protocol tokens as generic; extend its denylist to also include path tokens
like "credential" and "credentials" so that schema matching ignores those
generic path components; update the when-clause in isGenericSchemaToken
(referenced by function name) to include "credential" and "credentials"
alongside the existing "http", "https", "www", "schema", "schemas", "vc".
- Around line 537-541: The typeAwareFallback logic in MatchingVcsHelper.kt
currently returns true when hasTypeConstraint is false, which allows VCs that
failed non-type claim constraints to slip through; update the typeAwareFallback
method to fail closed by returning false when hasTypeConstraint is false (or
only return true if the fallback explicitly verifies the specific failed
non-type constraint), and ensure the change references the hasTypeConstraint
boolean and the typeAwareFallback function so callers of type/schema matching
cannot bypass failed claim constraints.

In
`@sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt`:
- Around line 158-164: In resolveFromJson, extend the JsonObject handling to
accept localized credentialName objects (e.g., credentialName: { "value": "..."
}) as well as primitive strings: after reading
vcObject.getAsSafeString("credentialName") and
vcObject.getAsSafeObject("credentialName"), check if the object exists and
extract its "value" string (falling back to nested vc handling), and similarly
for the nestedVc variable; update the resolution order in resolveFromJson to
return the extracted "value" when present before falling back to type/docType.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9541e940-5ad8-4dd0-9a0d-9bce2c0eade3

📥 Commits

Reviewing files that changed from the base of the PR and between dea74ba and d0357e1.

📒 Files selected for processing (6)
  • sample-application/sample-application/.kotlin/sessions/kotlin-compiler-16268922388281170111.salive
  • sample-application/sample-application/app/build.gradle.kts
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/MatchingCredentialsScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/ui/VPShareScreen.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/ovp/utils/MatchingVcsHelper.kt
  • sample-application/sample-application/app/src/main/java/com/example/samplecredentialwallet/utils/CredentialParser.kt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants