From e3b728ac7bc291fb26de6a7cae9f2e933e644c0e Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Thu, 5 Mar 2026 16:46:21 +0530 Subject: [PATCH 1/7] test case update --- .../TestCases.HostApp/Issues/Issue34211.cs | 107 ++++++++++++++++++ .../Tests/Issues/Issue34211.cs | 77 +++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue34211.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34211.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue34211.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue34211.cs new file mode 100644 index 000000000000..91da0a493ff9 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue34211.cs @@ -0,0 +1,107 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 34211, "Android display-size change causes parent and drawable children mismatch in .NET MAUI", PlatformAffected.Android)] +public class Issue34211 : ContentPage +{ + readonly Issue34211_Drawable _drawable = new(); + GraphicsView _graphicsView; + Label _statusLabel; + + public Issue34211() + { + _graphicsView = new GraphicsView + { + AutomationId = "Issue34211_GraphicsView", + BackgroundColor = Color.FromArgb("#F0F0F0"), + Drawable = _drawable, + }; + + _statusLabel = new Label + { + AutomationId = "Issue34211_StatusLabel", + Text = "Waiting for first draw...", + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(0, 0, 0, 10), + }; + + var checkButton = new Button + { + AutomationId = "Issue34211_CheckButton", + Text = "Check Size Match", + }; + checkButton.Clicked += (_, _) => _graphicsView.Invalidate(); + + // Mirror the original sample's layout: + // Label in Auto row, GraphicsView in * row so it fills remaining space + // and always has a non-zero size — no HeightRequest needed. + Content = new Grid + { + Padding = 20, + RowDefinitions = + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star }, + }, + Children = + { + _statusLabel, + checkButton, + _graphicsView, + } + }; + + Grid.SetRow(_statusLabel, 0); + Grid.SetRow(checkButton, 1); + Grid.SetRow(_graphicsView, 2); + + // Mirror the original: re-invalidate whenever the view resizes + _graphicsView.SizeChanged += (_, _) => + { +#if ANDROID + Android.Util.Log.Debug("ISSUE34211", $"[Issue34211.SizeChanged] GraphicsView={_graphicsView.Width:F1}x{_graphicsView.Height:F1} — calling Invalidate()"); +#endif + _graphicsView.Invalidate(); + }; + + // Mirror the original: update the label on every draw automatically + _drawable.OnDrawn = rect => + { +#if ANDROID + Android.Util.Log.Debug("ISSUE34211", $"[Issue34211.OnDrawn] dirtyRect={rect.Width:F1}x{rect.Height:F1} GraphicsView={_graphicsView.Width:F1}x{_graphicsView.Height:F1}"); +#endif + MainThread.BeginInvokeOnMainThread(() => + { + double viewW = _graphicsView.Width; + double viewH = _graphicsView.Height; + bool widthMatch = Math.Abs(viewW - rect.Width) <= 1.0; + bool heightMatch = Math.Abs(viewH - rect.Height) <= 1.0; + string status = widthMatch && heightMatch + ? "PASS: sizes match" + : $"FAIL: GraphicsView={viewW:F1}x{viewH:F1} Drawable={rect.Width:F1}x{rect.Height:F1}"; + + _statusLabel.Text = status; +#if ANDROID + Android.Util.Log.Debug("ISSUE34211", $"[Issue34211.StatusUpdate] {status}"); +#endif + }); + }; + } +} + +public class Issue34211_Drawable : IDrawable +{ + public Action OnDrawn { get; set; } + + public void Draw(ICanvas canvas, RectF dirtyRect) + { + canvas.FillColor = Colors.CornflowerBlue; + canvas.FillRectangle(dirtyRect); + + canvas.StrokeColor = Colors.DarkBlue; + canvas.StrokeSize = 3; + canvas.DrawRectangle(dirtyRect); + + OnDrawn?.Invoke(dirtyRect); + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34211.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34211.cs new file mode 100644 index 000000000000..81bb47d15a5c --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34211.cs @@ -0,0 +1,77 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue34211 : _IssuesUITest +{ + public Issue34211(TestDevice device) : base(device) { } + + public override string Issue => "Android display-size change causes parent and drawable children mismatch in .NET MAUI"; + + [Test] + [Category(UITestCategories.GraphicsView)] + public void DrawableDirtyRectMatchesGraphicsViewSizeAfterDisplayDensityChange() + { + // Wait for the GraphicsView to appear + App.WaitForElement("Issue34211_GraphicsView"); + + // Wait until the drawable has drawn at least once. + // The label auto-updates on every draw, so poll until it leaves "Waiting..." state. + App.WaitForElement(() => + { + var text = App.FindElement("Issue34211_StatusLabel")?.GetText() ?? string.Empty; + return text.StartsWith("PASS", StringComparison.Ordinal) || + text.StartsWith("FAIL", StringComparison.Ordinal) + ? App.FindElement("Issue34211_StatusLabel") + : null; + }, "Timed out waiting for GraphicsView initial draw"); + + // Verify initial draw is correct on all platforms + var initialStatus = App.FindElement("Issue34211_StatusLabel").GetText() ?? string.Empty; + Assert.That(initialStatus, Does.StartWith("PASS"), + $"Initial draw: GraphicsView and drawable sizes do not match. Actual: {initialStatus}"); + +#if ANDROID + // Android-only: trigger a real display-density change via adb. + // Background the app first — exactly as the user does: go to Settings, change + // Display size, come back. The Activity may be recreated (density is not in MAUI's + // configChanges), so we re-navigate to the issue page after foregrounding. + string originalDensity = ShellHelper.ExecuteShellCommandWithOutput("adb shell wm density").Trim(); + try + { + App.BackgroundApp(); + string newDensity = originalDensity.Contains("320", StringComparison.Ordinal) ? "280" : "320"; + ShellHelper.ExecuteShellCommand($"adb shell wm density {newDensity}"); + App.ForegroundApp(); + + // Activity may have been recreated — re-navigate to the issue page + App.WaitForElement("SearchBar"); + App.ClearText("SearchBar"); + App.EnterText("SearchBar", Issue); + App.WaitForElement("GoToTestButton"); + App.Tap("GoToTestButton"); + + // Wait for the label to reflect the post-density-change draw + App.WaitForElement(() => + { + var text = App.FindElement("Issue34211_StatusLabel")?.GetText() ?? string.Empty; + return text.StartsWith("PASS", StringComparison.Ordinal) || + text.StartsWith("FAIL", StringComparison.Ordinal) + ? App.FindElement("Issue34211_StatusLabel") + : null; + }, "Timed out waiting for draw after density change"); + + var statusAfterChange = App.FindElement("Issue34211_StatusLabel").GetText() ?? string.Empty; + Assert.That(statusAfterChange, Does.StartWith("PASS"), + $"GraphicsView and drawable sizes diverged after Android display-density change (issue #34211). " + + $"Actual: {statusAfterChange}"); + } + finally + { + ShellHelper.ExecuteShellCommand("adb shell wm density reset"); + } +#endif + } +} From 92e1e96b2ac2b5d432b03e332701389fce5c916b Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Thu, 5 Mar 2026 17:35:48 +0530 Subject: [PATCH 2/7] test sample --- .../GraphicsViewPage.xaml | 29 +++++++++ .../GraphicsViewPage.xaml.cs | 62 +++++++++++++++++++ .../Controls.Sample.Sandbox/MainPage.xaml | 9 ++- .../Controls.Sample.Sandbox/MainPage.xaml.cs | 7 ++- 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml create mode 100644 src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml.cs diff --git a/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml b/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml new file mode 100644 index 000000000000..65eeaa67260a --- /dev/null +++ b/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml.cs b/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml.cs new file mode 100644 index 000000000000..401af17fb25f --- /dev/null +++ b/src/Controls/samples/Controls.Sample.Sandbox/GraphicsViewPage.xaml.cs @@ -0,0 +1,62 @@ +namespace Maui.Controls.Sample; + +public partial class GraphicsViewPage : ContentPage +{ + private readonly SimpleDrawable _drawable = new SimpleDrawable(); + + public GraphicsViewPage() + { + InitializeComponent(); + + _drawable.OnDrawn = rect => + { + MainThread.BeginInvokeOnMainThread(() => + { + SizeLabel.Text = + $"GraphicsView : {DrawView.Width:F1} x {DrawView.Height:F1}\n" + + $"Drawable rect: {rect.Width:F1} x {rect.Height:F1}"; + + // Highlight mismatch in red + bool mismatch = + Math.Abs(DrawView.Width - rect.Width) > 1 || + Math.Abs(DrawView.Height - rect.Height) > 1; + SizeLabel.TextColor = mismatch ? Colors.Red : Colors.Green; + + Console.WriteLine($"SANDBOX: GraphicsView={DrawView.Width:F1}x{DrawView.Height:F1} Drawable={rect.Width:F1}x{rect.Height:F1} Mismatch={mismatch}"); + }); + }; + + DrawView.Drawable = _drawable; + + // Re-invalidate whenever the GraphicsView itself resizes + DrawView.SizeChanged += (_, __) => + { + DrawView.Invalidate(); + }; + } +} + +/// +/// Minimal drawable that reports its dirtyRect on every draw call. +/// After an Android display-size change while the app is running, +/// dirtyRect.Width/Height will differ from GraphicsView.Width/Height +/// even though the parent layout has updated — reproducing issue #34211. +/// +public class SimpleDrawable : IDrawable +{ + public Action? OnDrawn { get; set; } + + public void Draw(ICanvas canvas, RectF dirtyRect) + { + // Draw a filled rectangle so the mismatch is also visible + canvas.FillColor = Colors.CornflowerBlue; + canvas.FillRectangle(dirtyRect); + + // Draw a border so the drawable bounds are clearly visible + canvas.StrokeColor = Colors.DarkBlue; + canvas.StrokeSize = 3; + canvas.DrawRectangle(dirtyRect); + + OnDrawn?.Invoke(dirtyRect); + } +} diff --git a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml index 7363d18deadd..de16611a7533 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml +++ b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml @@ -1,5 +1,12 @@  + xmlns:local="clr-namespace:Maui.Controls.Sample" + Title="Issue #34211 Repro"> + +