From f0c416dcb0fe0e809d4e566417184ee370d083df Mon Sep 17 00:00:00 2001 From: Dan Nesfeder Date: Thu, 20 Aug 2026 12:37:14 -0500 Subject: [PATCH] Support camera access in the create-credential auth WebView Issuer authorization pages that run identity verification use getUserMedia for document and selfie capture. The auth WebView has no WebChromeClient, so page permission requests are silently denied and capture hangs. Declare the CAMERA permission and handle the WebView's VIDEO_CAPTURE permission requests: grant when the app already holds the runtime permission, otherwise ask the user first and answer the page from the dialog result. Requests that arrive while the dialog is showing are queued and answered together. All other resource types are denied. Co-Authored-By: Claude Fable 5 --- app/src/main/AndroidManifest.xml | 1 + .../createcred/CreateCredentialActivity.kt | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2513dba..5c6e47a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/credman/cmwallet/createcred/CreateCredentialActivity.kt b/app/src/main/java/com/credman/cmwallet/createcred/CreateCredentialActivity.kt index eb29fdc..6176463 100644 --- a/app/src/main/java/com/credman/cmwallet/createcred/CreateCredentialActivity.kt +++ b/app/src/main/java/com/credman/cmwallet/createcred/CreateCredentialActivity.kt @@ -1,16 +1,21 @@ package com.credman.cmwallet.createcred +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.service.credentials.CredentialProviderService import android.util.Log import android.view.ViewGroup +import android.webkit.PermissionRequest +import android.webkit.WebChromeClient import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -62,6 +67,21 @@ import com.credman.cmwallet.ui.theme.CMWalletTheme class CreateCredentialActivity : ComponentActivity() { private val viewModel: CreateCredentialViewModel by viewModels() + private val pendingWebPermissionRequests = mutableListOf() + + private val cameraPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + pendingWebPermissionRequests.forEach { request -> + if (granted) { + request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + } else { + request.deny() + } + } + pendingWebPermissionRequests.clear() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) { @@ -301,6 +321,35 @@ class CreateCredentialActivity : ComponentActivity() { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) + this.webChromeClient = object : WebChromeClient() { + override fun onPermissionRequest(request: PermissionRequest) { + val pageWantsCamera = + PermissionRequest.RESOURCE_VIDEO_CAPTURE in request.resources + val appHoldsCameraPermission = checkSelfPermission( + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + val permissionDialogAlreadyShowing = + pendingWebPermissionRequests.isNotEmpty() + + when { + !pageWantsCamera -> { + Log.d(TAG, "[AuthWebView] Denying ${request.resources.joinToString()} for ${request.origin}") + request.deny() + } + appHoldsCameraPermission -> { + Log.d(TAG, "[AuthWebView] Granting camera to ${request.origin}") + request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + } + else -> { + Log.d(TAG, "[AuthWebView] Asking for camera permission on behalf of ${request.origin}") + pendingWebPermissionRequests += request + if (!permissionDialogAlreadyShowing) { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + } + } + } this.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?,