Skip to content

[Android] Fix screenshot from webview content not working#30437

Closed
jsuarezruiz wants to merge 9 commits into
net10.0from
fix-30010
Closed

[Android] Fix screenshot from webview content not working#30437
jsuarezruiz wants to merge 9 commits into
net10.0from
fix-30010

Conversation

@jsuarezruiz

Copy link
Copy Markdown
Contributor

Description of Change

This PR introduce a hardware-accelerated capture path on before falling back to canvas or drawing-cache rendering. Also, updated the screenshot sample from Essentials and added an UITest.

Issues Fixed

Fixes #30010

@jsuarezruiz jsuarezruiz added t/bug Something isn't working platform/android area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info labels Jul 4, 2025
@PureWeen PureWeen added this to the .NET 10.0-rc1 milestone Jul 30, 2025
@PureWeen PureWeen moved this from Todo to In Progress in MAUI SDK Ongoing Aug 4, 2025
@PureWeen PureWeen modified the milestones: .NET 10.0-rc1, .NET 10.0-rc2 Aug 17, 2025
@PureWeen PureWeen moved this from In Progress to Changes Requested in MAUI SDK Ongoing Sep 3, 2025
@PureWeen PureWeen modified the milestones: .NET 10.0-rc2, Backlog Oct 6, 2025
@MauiBot

MauiBot commented May 11, 2026

Copy link
Copy Markdown
Collaborator

🤖 AI Summary

👋 @jsuarezruiz — new AI review results are available. Please review the latest session below.

📊 Review Session90dba4c · Merge branch 'net10.0' into fix-30010 · 2026-05-11 16:24 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: ANDROID

⚠️ verify-tests-fail.ps1 exited before writing a verification report. Diagnostics below.

Exit code: 1

Likely cause:

  • Build error before any test could run.
Gate output log (last 60 lines)
/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-maui-7c4f71ad' (7ffcf3a5-2a89-4afa-85e7-38e3cba6455f) has been disabled. (DevOps Activity ID: E5ECEB5B-5005-4137-A6B4-F4A5C31C80E9)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-android-1719a35b' (1880ee8b-9235-4d87-8c05-f870f04fad7a) has been disabled. (DevOps Activity ID: E50CFA74-9835-4208-8344-55FC4D5306FD)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
  Failed to restore /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 98 ms).
/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json.
/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json.
/home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-maui-7c4f71ad' (7ffcf3a5-2a89-4afa-85e7-38e3cba6455f) has been disabled. (DevOps Activity ID: E5ECEB5B-5005-4137-A6B4-F4A5C31C80E9)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-android-1719a35b' (1880ee8b-9235-4d87-8c05-f870f04fad7a) has been disabled. (DevOps Activity ID: E50CFA74-9835-4208-8344-55FC4D5306FD)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
  Failed to restore /home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 74 ms).
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-maui-7c4f71ad' (7ffcf3a5-2a89-4afa-85e7-38e3cba6455f) has been disabled. (DevOps Activity ID: E5ECEB5B-5005-4137-A6B4-F4A5C31C80E9)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-android-1719a35b' (1880ee8b-9235-4d87-8c05-f870f04fad7a) has been disabled. (DevOps Activity ID: E50CFA74-9835-4208-8344-55FC4D5306FD)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
  Failed to restore /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 12 ms).
/home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-maui-7c4f71ad' (7ffcf3a5-2a89-4afa-85e7-38e3cba6455f) has been disabled. (DevOps Activity ID: E5ECEB5B-5005-4137-A6B4-F4A5C31C80E9)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-android-1719a35b' (1880ee8b-9235-4d87-8c05-f870f04fad7a) has been disabled. (DevOps Activity ID: E50CFA74-9835-4208-8344-55FC4D5306FD)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
  Failed to restore /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 26 ms).
/home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-maui-7c4f71ad/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-maui-7c4f71ad' (7ffcf3a5-2a89-4afa-85e7-38e3cba6455f) has been disabled. (DevOps Activity ID: E5ECEB5B-5005-4137-A6B4-F4A5C31C80E9)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj : error NU1301: Unable to load the service index for source https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-android-1719a35b/nuget/v3/index.json. [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
/home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj : error NU1301:   Response status code does not indicate success: 404 (Not Found - TF1600012: The feed 'darc-pub-dotnet-android-1719a35b' (1880ee8b-9235-4d87-8c05-f870f04fad7a) has been disabled. (DevOps Activity ID: E50CFA74-9835-4208-8344-55FC4D5306FD)). [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj]
  Failed to restore /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 18 ms).
  Failed to restore /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 779 ms).
❌ Build/deploy failed with exit code 1
❌ Build or deployment failed
[2026-05-11 16:15:55] 
[2026-05-11 16:15:55] ==========================================
[2026-05-11 16:15:55] VERIFICATION RESULTS
[2026-05-11 16:15:55] ==========================================
[2026-05-11 16:15:55] ✅ Tests FAILED without fix (expected - issue detected)
[2026-05-11 16:15:55] ❌ Tests FAILED with fix (unexpected!)
[2026-05-11 16:15:55]    The fix doesn't resolve the issue, or there's another problem.
[2026-05-11 16:15:55] 
[2026-05-11 16:15:55] Summary:
[2026-05-11 16:15:55]   - Tests WITHOUT fix: FAIL ✅ (expected)
[2026-05-11 16:15:55]   - Tests WITH fix: FAIL ❌ (should pass!)
📄 Markdown report saved to: /home/vsts/work/1/s/CustomAgentLogsTmp/PRState/30437/verify-tests-fail/verification-report.md
╔═══════════════════════════════════════════════════════════╗
║              VERIFICATION FAILED ❌                       ║
╠═══════════════════════════════════════════════════════════╣
║  Tests FAILED with fix (should pass)                      ║
║  - Fix doesn't resolve the issue or test is broken        ║
║                                                           ║
║  Possible causes:                                         ║
║  1. Wrong fix files specified                             ║
║  2. Tests don't actually test the fixed behavior          ║
║  3. The issue was already fixed in base branch            ║
║  4. Build caching - try clean rebuild                     ║
╚═══════════════════════════════════════════════════════════╝
🏷️  Updating verification labels on PR #30437...
   Adding: s/ai-reproduction-failed
✅ Labels updated successfully

🔍 Pre-Flight — Context & Validation

Pre-Flight — PR #30437

PR summary

Reported bug (issue #30010)

  • Calling Screenshot.CaptureAsync() and binding the resulting stream to Image.Source works in Debug or .NET 9, but the resulting image is invisible/blank in Release builds on .NET 10 when the page contains a WebView.
  • Repro: WebViewSample.zip, button below WebView → screenshot is captured but Image renders empty.

Existing fix in PR

src/Essentials/src/Screenshot/Screenshot.android.cs — adds a new "hardware-accelerated" capture branch:

Render(view):
    if view.IsHardwareAccelerated && API ≥ 24:
        haBitmap = RenderHardwareAccelerated(view)
        if haBitmap != null return haBitmap
    fallback → RenderUsingCanvasDrawing → RenderUsingDrawingCache

RenderHardwareAccelerated ultimately calls RenderWithTemporarySoftwareMode(view), which:

  1. Saves view.LayerType.
  2. Calls view.SetLayerType(LayerType.Software, null) and view.Invalidate().
  3. Thread.Sleep(50) on the calling thread.
  4. Creates Argb8888 bitmap, view.Draw(canvas).
  5. Restores layer type in finally.

Other PR changes:

  • Controls.TestCases.HostApp.csproj — adds ProjectReference to Essentials.csproj.
  • MauiProgram.cs — adds appBuilder.ConfigureEssentials().
  • Issue30010.xaml + Issue30010.xaml.cs — repro page with WebView + Take Screenshot button + result Image. ViewModel uses ImageSource.FromStream(() => stream) (note: stream factory returns the same captured stream instance every call — can only be consumed once).
  • Issue30010.cs (Android-only UITest) — taps button, Thread.Sleep(1000), VerifyScreenshot("Issue30010TakeScreenshotFunctionality").
  • New baseline PNG snapshots/android/Issue30010TakeScreenshotFunctionality.png.
  • Essentials/samples/Samples/View/ScreenshotPage.xaml — adds WebView + Switch to the sample.

Risk assessment

Concern Severity Notes
Thread.Sleep(50) on the calling thread High CaptureAsync is typically invoked on the UI thread (Activity/DecorView). Sleeping the UI thread is an ANR/jank risk; also 50 ms is heuristic — no guarantee the hardware layer is rebuilt in software mode within 50 ms.
SetLayerType(Software) applied to whole DecorView root High When Render is called with the activity's DecorView root, this flips every view subtree to software rendering and then restores it. Causes a visible flash/relayout, expensive on a complex screen.
view.IsHardwareAccelerated is true for ~all views on modern Android High The new branch runs unconditionally on every screenshot, adding the 50 ms sleep + layer toggle every single time, even when the canvas path would already have worked.
Doesn't actually use the GPU (no PixelCopy) Med Despite the name "hardware-accelerated", the implementation is still a software canvas draw — it just toggles the layer type. The proper Android API for capturing hardware-accelerated content (incl. SurfaceView/TextureView and the WebView's SurfaceView on API ≥ 26) is PixelCopy.Request.
view.RootView check on a view that itself may already be the root Med When CaptureAsync(Activity) runs, view is already the DecorView root, so the early-return-on-null is moot.
Repro ViewModel uses a one-shot stream as factory Med ImageSource.FromStream(() => stream) captures the local stream — the MAUI image loading may dispose or fully read it once, leaving subsequent loads blank. This may be part of the originally observed bug rather than the platform code.
Test uses Thread.Sleep(1000) + VerifyScreenshot instead of waiting for the result Image element Low Test is timing-fragile.
Build error risk: Controls.TestCases.HostApp adding Essentials.csproj reference Low Already partly referenced through Maui meta-package; double reference can cause duplicate-type warnings if not handled.

Acceptance criteria from issue

  1. Screenshot of a page containing a WebView in Release builds on Android renders into the result Image.
  2. No regression for non-WebView pages.
  3. No ANR / no UI thread jank.
  4. New UITest covers the scenario.

Notes for downstream phases

  • The PR's core mechanic (toggle layer + sleep + canvas draw) is the same as RenderUsingCanvasDrawing with a thread sleep. The likely real root cause for the Release regression is the WebView's underlying SurfaceView no longer being captured by view.Draw(canvas) once hardware rendering is in use.
  • The Android-recommended fix is PixelCopy.Request (API 24/26+), which copies pixels from the rendered surface directly.
  • Try-fix candidates should explore: PixelCopy-based capture; targeted WebView handling; layer-toggle without sleep (post-frame callback); and a "do nothing but fix the stream lifecycle" alternative.

🔧 Fix — Analysis & Comparison

Try-Fix Aggregate Narrative — PR #30437

Four independent candidate fixes were generated against origin/net10.0 for the Android screenshot-of-WebView regression (#30010). Each candidate loaded a different domain dimension from the maui-expert-reviewer rubric so the search space stays diverse:

Candidate Dimension One-line summary Same approach as PR?
try-fix-1 Performance / UI-thread health Async PreDraw wait, scope HW path to actual surface descendants, sample-based blank check Same family (layer toggle) but no UI-thread sleep
try-fix-2 Android API correctness PixelCopy.Request (API ≥ 26) — the canonical Android API for snapshotting rendered surfaces Different (no layer mutation)
try-fix-3 Targeted minimal fix Walk the tree, draw each WebView on top of the canvas result Different (additive composite)
try-fix-4 Architecture / reframe Pre-fill the bitmap with the view background so transparent surface regions render opaque Different (root-cause hypothesis)

Gate context

The supplied Gate phase reported ❌ FAILED for the PR head. That signal applies to the PR's implementation and does not propagate to alternative candidates — but it tells us that the PR's mechanism (canvas draw with the entire DecorView forced to software layer + 50 ms sleep) does not reliably produce the snapshot baseline on Android.

This favors candidates that take a fundamentally different mechanism:

  • try-fix-2 (PixelCopy) — independent of canvas draw entirely.
  • try-fix-3 (WebView composite) — independent of layer toggling.

…over candidates that just refine the PR's approach (try-fix-1) or that hinge on an unverified hypothesis (try-fix-4).

Recommended ordering

  1. try-fix-2 — PixelCopy is the textbook Android answer; highest confidence in correctness and lowest UI-thread risk.
  2. try-fix-3 — minimal-blast-radius WebView-specific patch; safe even if PixelCopy isn't viable in this codebase for some reason.
  3. try-fix-1 — useful as a perf hardening on top of whichever capture method ships, but doesn't change the underlying capture mechanic.
  4. try-fix-4 — interesting hypothesis, lowest cost, but high risk of not addressing the actual symptom captured by the new UITest baseline.

See each candidate's try-fix-{N}/content.md for full diff and detailed reasoning.


📋 Report — Final Recommendation

Comparative Report — PR #30437

Candidate ranking matrix

Candidate Capture mechanism UI-thread risk API safety Likely passes new UITest? Notes
pr Layer→Software toggle + Thread.Sleep(50) + canvas draw High — sleeps UI thread on every call Low — IsHardwareAccelerated is true everywhere, so the slow path runs always ❌ Gate FAILED The gate signal is the most authoritative info we have.
pr-plus-reviewer Same as pr but PreDraw wait + scoped toggle + better error logging + fixed VM stream Medium — still toggles layer; PreDraw wait replaces sleep Better Inherited risk from pr. Mechanism is the same family; while it improves perf and code health, it doesn't change the underlying capture method that the gate found insufficient.
try-fix-1 Same family as pr, async PreDraw + descendant gate Low Better Same risk pattern as pr-plus-reviewer w.r.t. baseline.
try-fix-2 PixelCopy.Request (API ≥ 26) None High Most likely ✅ — independent of the canvas-draw mechanic the gate exposed as broken. Industry-standard Android API for hardware surface capture (WebView, SurfaceView, TextureView).
try-fix-3 Canvas draw + per-WebView composite None High Likely ✅ — synthesises the missing WebView region Smallest blast radius; safe even if PixelCopy is unavailable.
try-fix-4 Canvas draw + opaque background fill None High Lowest — hypothesis-driven Trivial change; if the bug is only transparent visibility this wins, but the snapshot baseline likely expects real WebView pixels.

Reasoning

  1. The gate result is decisive. The submitted PR mechanism failed the regression test it ships with. Per the explicit rule "candidates that failed regression tests MUST be ranked lower than candidates that passed them", pr is ranked at the bottom by definition.
  2. pr-plus-reviewer is the PR's mechanism with polish. Since the polish (no UI sleep, scoped layer toggle, fixed stream lifecycle) doesn't alter the capture method, it inherits the gate-failure risk. It improves PR quality but does not change the outcome the gate evaluated.
  3. try-fix-1 is similarly a refinement of the same family; same caveat.
  4. try-fix-4 is a one-line bet on a hypothesis that the bitmap is captured fine and only its transparency causes invisibility. The snapshot baseline (which presumably contains the WebView's HTML content rendered) makes this hypothesis less likely; ranked low.
  5. try-fix-3 introduces per-WebView compositing — a controllable, narrow, deterministic addition that fills exactly the region that hardware surfaces leave blank in canvas draws. It is reliable and side-effect-free for non-WebView trees.
  6. try-fix-2 (PixelCopy) is the canonical Android way to do this. It is async, doesn't manipulate the view tree, doesn't require any sleeps, and works for all hardware-surface views (WebView/SurfaceView/TextureView). It is the highest-confidence winner.

Winner: try-fix-2

try-fix-2 replaces the brittle "force software layer + sleep + canvas draw" with PixelCopy.Request(window, rect, bitmap, listener, handler) (API ≥ 26), which is Android's recommended way to snapshot hardware-rendered surfaces — including a WebView's SurfaceView. It:

Comparison with second place (try-fix-3)

try-fix-3 is a strong runner-up — minimal API surface, no platform-version gates, easy to audit. The reasons it lost to try-fix-2:

  • try-fix-3 is WebView-specific; identical regressions affecting other hardware surfaces (Maps MapView, VideoView, custom GL views) are not addressed.
  • try-fix-2 does not require walking the view tree on every screenshot.
  • try-fix-2 is the documented Android best practice; try-fix-3 reuses the underlying canvas-draw mechanism the gate already flagged as insufficient.

Why no pr candidate wins

Both pr and pr-plus-reviewer keep the failed mechanism. Per the gate ranking rule they must rank below candidates with passing test signals.

Risk callouts on the winner

  • PixelCopy.Request(Window, ...) requires API 26. For API 24–25, only the surface-based overload exists; the candidate falls back to canvas-draw on those versions. (Same coverage as today.)
  • For CaptureAsync(View) the window is obtained via view.Context as Activity. Custom ContextWrappers that don't expose the activity will fall back to the canvas path — same behavior as today, no regression.
  • PixelCopy on hardware-protected surfaces returns ErrorSecure; the candidate returns null and the fallback path runs, identical to today's secure-window handling.

Recommendation to maintainers

Adopt try-fix-2 and additionally pull in the non-platform improvements from pr-plus-reviewer:

  • Fix the repro page's ImageSource.FromStream(() => stream) to use ImageSource.FromStream(() => new MemoryStream(bytes)).
  • Replace Thread.Sleep(1000) in the UITest with App.WaitForElement("ResultImage").

@MauiBot MauiBot added s/agent-review-incomplete s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 11, 2026
@kubaflo

kubaflo commented May 11, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of #35384, which replaces the software layer toggle approach with Android's PixelCopy API as recommended by the AI review.

Key changes from this PR:

  • PixelCopy.Request instead of SetLayerType(Software) + Thread.Sleep(50) — no UI thread blocking, correct hardware surface capture
  • Fixed test page stream lifecycle (new MemoryStream(bytes) instead of one-shot stream)
  • Removed Thread.Sleep(1000) from UITest — waits for UI elements instead
  • No extra project references or ConfigureEssentials() needed
  • C#-only test page per repo conventions

Thank you @jsuarezruiz for identifying the issue and the initial fix — you're listed as co-author on the new PR. 🙏

@kubaflo kubaflo closed this May 11, 2026
@github-project-automation github-project-automation Bot moved this from Changes Requested to Done in MAUI SDK Ongoing May 11, 2026
kubaflo added a commit that referenced this pull request May 28, 2026
### Description of Change

Replaces the approach in #30437 with Android's canonical `PixelCopy` API
(API 26+) for capturing hardware-accelerated surfaces, as recommended by
the AI review.

**Problem:** Calling `Screenshot.CaptureAsync()` on a page containing a
`WebView` in Release builds on Android results in a blank/invisible
image. The canvas-based `view.Draw()` approach cannot capture
hardware-rendered surfaces like WebView's internal `SurfaceView`.

**Solution:** Use `PixelCopy.Request(Window, Rect, Bitmap, ...)` which
is the Android-recommended API for snapshotting rendered surfaces —
including WebView, SurfaceView, TextureView, and other
hardware-accelerated views.

**Key improvements over #30437:**
- **No UI thread blocking** — `PixelCopy` is callback-based and fully
async, eliminating the `Thread.Sleep(50)` on the UI thread
- **No layer type mutation** — avoids toggling the entire DecorView to
software rendering (which caused visual flash and expensive relayout)
- **Correct API** — `PixelCopy` is the documented Android best practice
for hardware surface capture (API 26+)
- **Graceful fallback** — falls back to existing canvas draw → drawing
cache paths on pre-API-26 devices or PixelCopy failure
- **Fixed test stream lifecycle** — uses `new MemoryStream(bytes)`
factory pattern instead of capturing a one-shot stream
- **Removed `Thread.Sleep` from UITest** — waits for UI elements instead
- **C#-only test page** — per repo conventions, no unnecessary XAML
- **No extra project references** — `Screenshot` is already accessible
through MAUI meta-package

Based on the work by @jsuarezruiz in #30437.

### Issues Fixed

Fixes #30010
Supersedes #30437

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jun 2, 2026
### Description of Change

Replaces the approach in #30437 with Android's canonical `PixelCopy` API
(API 26+) for capturing hardware-accelerated surfaces, as recommended by
the AI review.

**Problem:** Calling `Screenshot.CaptureAsync()` on a page containing a
`WebView` in Release builds on Android results in a blank/invisible
image. The canvas-based `view.Draw()` approach cannot capture
hardware-rendered surfaces like WebView's internal `SurfaceView`.

**Solution:** Use `PixelCopy.Request(Window, Rect, Bitmap, ...)` which
is the Android-recommended API for snapshotting rendered surfaces —
including WebView, SurfaceView, TextureView, and other
hardware-accelerated views.

**Key improvements over #30437:**
- **No UI thread blocking** — `PixelCopy` is callback-based and fully
async, eliminating the `Thread.Sleep(50)` on the UI thread
- **No layer type mutation** — avoids toggling the entire DecorView to
software rendering (which caused visual flash and expensive relayout)
- **Correct API** — `PixelCopy` is the documented Android best practice
for hardware surface capture (API 26+)
- **Graceful fallback** — falls back to existing canvas draw → drawing
cache paths on pre-API-26 devices or PixelCopy failure
- **Fixed test stream lifecycle** — uses `new MemoryStream(bytes)`
factory pattern instead of capturing a one-shot stream
- **Removed `Thread.Sleep` from UITest** — waits for UI elements instead
- **C#-only test page** — per repo conventions, no unnecessary XAML
- **No extra project references** — `Screenshot` is already accessible
through MAUI meta-package

Based on the work by @jsuarezruiz in #30437.

### Issues Fixed

Fixes #30010
Supersedes #30437

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jun 11, 2026
### Description of Change

Replaces the approach in #30437 with Android's canonical `PixelCopy` API
(API 26+) for capturing hardware-accelerated surfaces, as recommended by
the AI review.

**Problem:** Calling `Screenshot.CaptureAsync()` on a page containing a
`WebView` in Release builds on Android results in a blank/invisible
image. The canvas-based `view.Draw()` approach cannot capture
hardware-rendered surfaces like WebView's internal `SurfaceView`.

**Solution:** Use `PixelCopy.Request(Window, Rect, Bitmap, ...)` which
is the Android-recommended API for snapshotting rendered surfaces —
including WebView, SurfaceView, TextureView, and other
hardware-accelerated views.

**Key improvements over #30437:**
- **No UI thread blocking** — `PixelCopy` is callback-based and fully
async, eliminating the `Thread.Sleep(50)` on the UI thread
- **No layer type mutation** — avoids toggling the entire DecorView to
software rendering (which caused visual flash and expensive relayout)
- **Correct API** — `PixelCopy` is the documented Android best practice
for hardware surface capture (API 26+)
- **Graceful fallback** — falls back to existing canvas draw → drawing
cache paths on pre-API-26 devices or PixelCopy failure
- **Fixed test stream lifecycle** — uses `new MemoryStream(bytes)`
factory pattern instead of capturing a one-shot stream
- **Removed `Thread.Sleep` from UITest** — waits for UI elements instead
- **C#-only test page** — per repo conventions, no unnecessary XAML
- **No extra project references** — `Screenshot` is already accessible
through MAUI meta-package

Based on the work by @jsuarezruiz in #30437.

### Issues Fixed

Fixes #30010
Supersedes #30437

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/ai-reproduction-failed t/bug Something isn't working

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants