Skip to content

[Android] Make system chrome follow MAUI bar colors (opt-in) - #35463

Merged
kubaflo merged 27 commits into
net11.0from
fix/android-system-chrome
Aug 7, 2026
Merged

[Android] Make system chrome follow MAUI bar colors (opt-in)#35463
kubaflo merged 27 commits into
net11.0from
fix/android-system-chrome

Conversation

@jfversluis

@jfversluis jfversluis commented May 15, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

This PR adds an explicitly opt-in Android behavior that makes status/navigation bar background colors follow effective MAUI NavigationPage, Shell, TabbedPage, and modal chrome colors.

Behavior model:

  • Default / property omitted: existing Android theme/platform-owned system chrome behavior is preserved.
  • Opt-in: set <MauiAndroidSystemBarsUseMauiChrome>true</MauiAndroidSystemBarsUseMauiChrome>.

The Android Controls device-test app opts in conditionally so the new behavior remains covered in CI, while an explicit false override validates legacy behavior. The standard UI-test HostApp remains on the legacy default, so this opt-in feature does not require broad snapshot churn.

Implementation details

  • Adds the internal AndroidSystemChrome helper to centralize gated status/navigation bar background updates.
  • Handles solid, linear-gradient, and radial-gradient chrome backgrounds using the color visible at the relevant top/bottom edge.
  • Updates NavigationPage, Shell toolbar/tabs/bottom navigation, TabbedPage, and modal windows.
  • Replays the latest initial bottom-chrome update after the native bottom view attaches, avoiding dropped startup colors while preventing stale queued values from overriding newer updates.
  • Restores captured native system-bar colors when MAUI has no effective chrome background.
  • Preserves developer/theme control of status/navigation bar icon appearance; this PR changes backgrounds only.
  • Keeps modal dialog chrome isolated from the underlying Activity window.
  • Keeps Shell system bars aligned with effective MAUI default colors when no explicit Shell color is supplied.
  • Preserves upstream Shell brush support while layering system-chrome synchronization onto it.
  • Adds no public API.

Compatibility

RuntimeFeature.UseMauiAndroidSystemBarBackgrounds defaults to false. All new chrome-update entry points return without changing AppBar or system-bar state when disabled. The MSBuild property is emitted as the corresponding runtime host configuration option only when supplied.

Do not try to toggle this feature per test after a trimmed Android app starts: FeatureSwitchDefinition can be linker constant-folded. Disabled and enabled behavior must be tested as separate compiled configurations, as done here.

Tests and validation

Validated after rebasing onto current net11.0 using MAUI's API 30 Emulator_30, the same emulator profile used by Android UI tests, plus an API 35 Pixel emulator.

Explicitly disabled (MauiAndroidSystemBarsUseMauiChrome=false):

  • Generated runtime config contains Microsoft.Maui.RuntimeFeature.UseMauiAndroidSystemBarBackgrounds=false.
  • Final API 30 TabbedPage matrix after the initial-bottom fix: 51/51 passed.
  • Earlier broad Controls device-test matrix: NavigationPage 23/23, TabbedPage 50/50, Toolbar 14/14 — 87/87 passed.
  • NavigationPage legacy visual capture differed from the current net11.0 baseline by 78 of 1,873,800 pixels (0.0042%) and was approximately 56× closer to the legacy baseline than the opt-in image.

Explicitly enabled (MauiAndroidSystemBarsUseMauiChrome=true):

  • Deterministic initial-bottom regression: fails without the attach replay and passes with it.
  • Final API 30 TabbedPage matrix: 50/51 passed; only the pre-existing MovingBetweenMultiplePagesWithNestedNavigationPages timeout variant failed, while the new regression passed.
  • Final API 35 TabbedPage matrix: 49/51 passed; only two pre-existing nested-navigation timeout variants failed, while the initial-bottom path passed.
  • Earlier broad Controls device-test matrix: NavigationPage 23/23, TabbedPage 50/50, Toolbar 14/14 — 87/87 passed.
  • Broad Modal-category run: 88/88 passed, including dialog-vs-Activity system-bar isolation.
  • Explicit opt-in Appium/manual captures demonstrate the intended NavigationPage and TabbedPage system-chrome changes; recordings are linked in the proof comment.

The Shell Appium scenario was attempted, but the harness timed out during accessibility/root-window setup before reaching its chrome assertion. This was not a product assertion failure; device tests and recorded manual interaction cover the affected Shell paths.

Issues Fixed

N/A

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35463

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35463"

@kubaflo

kubaflo commented May 20, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/regression-check -p android

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

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if it should target the net11 branch instead.

@kubaflo

kubaflo commented May 23, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread src/Controls/src/Core/Platform/Android/AndroidSystemChrome.cs
@MauiBot MauiBot added the s/agent-fix-win AI found a better alternative fix than the PR label May 24, 2026
@NirmalKumarYuvaraj NirmalKumarYuvaraj added the partner/syncfusion Issues / PR's with Syncfusion collaboration label May 26, 2026
@kubaflo

kubaflo commented May 27, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread src/Core/src/Platform/Android/WindowExtensions.cs Outdated
@MauiBot

This comment has been minimized.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We probably need to update snapshots

@kubaflo

kubaflo commented May 28, 2026

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

jfversluis and others added 7 commits August 6, 2026 14:39
Restore the captured native AppBar drawable before applying solid-color tints so non-solid brush drawables cannot remain underneath.

Prefer the toolbar style title text color before falling back to theme-derived foreground colors when BarTextColor is unset.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a RuntimeFeature-backed MSBuild switch so apps can keep Android theme/platform ownership of system bar background colors while MAUI keeps matching system bars to MAUI chrome by default.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Merge the latest net11.0 changes and remove the fixed delay from the Android system bar opt-out regression test so the negative assertion runs synchronously after changing BarBackgroundColor.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Refresh Android and Android API 36 visual baselines for the new system chrome behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Correct the Android accessibility checked-state assignment after the AndroidX API update and register the current Activity for the Window handler status-bar test setup.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3f200d22-045a-4896-97a0-65fb75fbc45d
Keep existing Android system chrome behavior by default, explicitly opt test apps into the new behavior, isolate modal windows from their underlying Activity, and keep Shell system bars aligned with effective default chrome colors. Remove the unrelated RadioButton accessibility change from this PR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3f200d22-045a-4896-97a0-65fb75fbc45d
Copilot AI review requested due to automatic review settings August 6, 2026 12:40
@jfversluis
jfversluis force-pushed the fix/android-system-chrome branch from 2f0d15b to efb5346 Compare August 6, 2026 12:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@jfversluis jfversluis changed the title Make Android system chrome follow MAUI bar colors [Android] Make system chrome follow MAUI bar colors (opt-in) Aug 6, 2026
@jfversluis

jfversluis commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Final rebase and empirical Android proof

Rebased onto current net11.0 (3624db4d3b) and pushed as efb5346c3e.

Compatibility contract proven

  • Default / omitted property: RuntimeFeature.UseMauiAndroidSystemBarBackgrounds defaults to false; the new centralized Android chrome entry points return without changing AppBar or system-bar state.
  • Explicit disabled build: generated runtime config contains Microsoft.Maui.RuntimeFeature.UseMauiAndroidSystemBarBackgrounds=false.
  • Explicit opt-in build: <MauiAndroidSystemBarsUseMauiChrome>true</MauiAndroidSystemBarsUseMauiChrome> enables the new behavior.
  • The two modes were compiled and deployed separately because linker feature-switch constant folding makes an in-process toggle an invalid test strategy for trimmed Android APKs.

API 30 emulator results

Compiled mode NavigationPage TabbedPage Toolbar Result
Explicit disabled / legacy 23/23 50/50 14/14 87/87
Explicit opt-in 23/23 50/50 14/14 87/87

The opt-in build also passed a broad 88/88 Modal-category run, including a regression assertion proving modal chrome updates the dialog window without recoloring the underlying Activity.

Visual evidence

  • Legacy NavigationPage capture differed from current net11.0 by only 78 / 1,873,800 pixels (0.0042%) and was approximately 56× closer to the legacy baseline than to the opt-in baseline.
  • Explicit opt-in Appium and manual captures demonstrate the intended NavigationPage and TabbedPage system-chrome changes. The standard UI-test HostApp now remains on the legacy default, so no snapshot baselines are changed by this PR.
  • Shell Appium automation was attempted, but Appium timed out during accessibility/root-window setup before reaching the chrome assertion; this was not a product assertion failure. The affected Shell paths are covered by device tests and recorded interaction.

Legacy/default behavior recording:
https://github.com/user-attachments/assets/9757f99b-320a-4d74-b503-89d72819f0c8

Explicit opt-in behavior recording:
https://github.com/user-attachments/assets/0cab7f8a-6980-4702-92a6-c67f481eb65c

I also removed an unrelated RadioButton accessibility change from the PR, preserved the newer upstream Shell brush behavior during conflict resolution, fixed Shell default-color synchronization, added dialog-vs-Activity isolation, and removed the broad Android snapshot refresh so the final PR changes only 18 source/test files.

Based on the compiled switch values, centralized disabled guards, visual comparison, and clean 87/87 matrices in both configurations, the activation boundary is demonstrated: existing behavior remains the default, and the new MAUI-owned Android system-chrome behavior only appears after explicit opt-in.

Keep the standard UI test host on the legacy default for this opt-in feature and restore unrelated Android visual baselines. Explicit opt-in and opt-out behavior remains covered by separately compiled device-test configurations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3f200d22-045a-4896-97a0-65fb75fbc45d
Copilot AI review requested due to automatic review settings August 6, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (3)

src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs:303

  • UpdateOverflowIconColor applies a black/white fallback tint (via GetDefaultForegroundColor) even when neither IconColor nor BarTextColor is set. That overwrites the native/theme default overflow icon tint and is a behavior change outside the new system-bar feature gate.
			var iconColor = toolbar.IconColor ?? toolbar.BarTextColor ?? GetDefaultForegroundColor();
			if (iconColor is null)
			{
				overflowIcon.ClearColorFilter();
			}
			else
			{
				overflowIcon.SetColorFilter(iconColor.ToPlatform(), FilterMode.SrcAtop);
			}

src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs:255

  • UpdateNavigationIconColor falls back to GetDefaultForegroundColor (black/white by RequestedTheme) before consulting the theme-derived DrawerArrowDrawable default. This can override a custom toolbar theme's default navigation icon color when IconColor/BarTextColor are unset.
				else if (GetDefaultForegroundColor() is { } defaultForegroundColor)
				{
					icon.Color = defaultForegroundColor.ToPlatform().ToArgb();
				}
				else if (GetDefaultNavigationIconColor(nativeToolbar) is int defaultNavigationIconColor)
				{
					icon.Color = defaultNavigationIconColor;
				}

src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarAppearanceTracker.cs:60

  • ShellToolbarAppearanceTracker only updates system chrome when the appearance explicitly provided a background (passes null otherwise), even though it always assigns a default BarBackground. This prevents status-bar sync with Shell's effective default background when no explicit Shell background is supplied.
			AndroidSystemChrome.UpdateTopChrome(
				toolbar,
				background is null ? null : shellToolbar.BarBackground);
  • Files reviewed: 17/18 changed files
  • Comments generated: 0 new

@kubaflo

kubaflo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Android revalidation after new commits — ❌ NOT PASS

Validated current head 607caf7db1f against previously validated 2f0d15b386e0.

Environment: Google sdk_gphone64_arm64 emulator (emulator-5554), Android API 36, 1080×2424 gesture-navigation display, net11.0-android (resolved Android 37.0). The generated runtime config confirmed UseMauiAndroidSystemBarBackgrounds=true for the opt-in run.

Matrix: NavigationPage solid/live update/gradient; top TabbedPage solid/gradient/solid; bottom TabbedPage initial/live update; modal LightGreen→Blue with distinct dialog window/root, icon flags, and Activity restoration; Shell explicit toolbar/bottom colors; Shell effective defaults with mixed icon flags.

Results:

  • Explicit opt-in: 14/17 rendered checks matched. On the directly comparable prior matrix, 11/13 matched (previous head: 13/13).
  • Three repeatable initial bottom-edge failures remained after an additional 2.5 s settle and a second complete run:
    • Bottom TabbedPage expected Green #2E7D32, dominant rendered edge #D5890A; live Purple update then matched.
    • Shell expected initial Teal #00897B, dominant edge #CC8B19; live Green update then matched.
    • Shell effective default expected White #FFFFFF, dominant edge #FFA333; top default #2C3E50 and mixed icon flags passed.
  • Default/property-omitted control: the original 13/13 checks remained legacy/mismatched; overall 15/17 mismatched, with the two Shell default-color checks naturally matching the theme defaults.
  • NavigationPage, top TabbedPage, modal isolation/live update, post-modal restoration, Shell top colors, and bottom live updates passed. No app crash/exception was found; the test harness exited nonzero intentionally for the three product mismatches.

Verdict: current head is not a PASS. The failure pattern is consistent with an initial bottom-view attach/window-resolution timing issue; subsequent property changes synchronize the navigation bar correctly.

Fresh API 36 comparison at head 607caf7db1f

Explicit opt-in confirmation recording (current head):
https://github.com/user-attachments/assets/d9e2b6f6-a1e2-416c-85a9-032eecdc5272

Default/property-omitted control recording:
https://github.com/user-attachments/assets/0a5ba823-40bd-4db6-be63-1787b43d22b7

Caveats: the control is the documented default/property-omitted legacy behavior, not a historical base checkout. I locally changed only the Sandbox script target from net10.0-android to net11.0-android and opted the Sandbox project in for the enabled run; no PR code was modified or pushed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3f200d22-045a-4896-97a0-65fb75fbc45d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3f200d22-045a-4896-97a0-65fb75fbc45d
Copilot AI review requested due to automatic review settings August 6, 2026 22:09
@jfversluis

Copy link
Copy Markdown
Member Author

Addressed the initial bottom-edge failures reported in the API 36 empirical revalidation at head ed2c18786e.

Root cause: initial bottom TabbedPage/Shell appearance updates can run before the native bottom view is attached. The Activity-window ownership check intentionally returns no window at that point, so the initial system-chrome update was dropped. Later property changes worked because the view was attached by then.

Fix: AndroidSystemChrome.UpdateBottomChrome now retains only the latest pending brush for an unattached view and replays it through the existing View.OnLoaded path after attachment. A newer attached update cancels the pending replay, including an already-queued callback, so stale startup state cannot overwrite a newer value. Activity-root ownership remains enforced, so dialog-hosted views still cannot recolor the underlying Activity.

Empirical proof:

  • Deterministic regression test: the pre-attach bottom update fails without this production fix and passes with it.
  • API 30 opt-in TabbedPage matrix: 50/51; the new regression passed, with only the known nested-navigation timeout variant failing.
  • API 35 opt-in TabbedPage matrix: 49/51; initial-bottom coverage passed, with only two known nested-navigation timeout variants failing.
  • API 30 explicit opt-out TabbedPage matrix: 51/51.
  • Build succeeds and the PR still contains zero snapshot changes.

The three API 36 failures all share this central bottom-chrome path (TabbedPage initial color, Shell explicit initial color, and Shell effective default initial color), while their later live updates already passed. Please rerun the same rendered API 36 matrix against ed2c18786e; that exact gesture-navigation pixel harness is the final direct confirmation for the reported scenarios.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (2)

src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarAppearanceTracker.cs:60

  • When appearance.Background and appearance.BackgroundColor are both unset, shellToolbar.BarBackground is still set to the default background, but UpdateTopChrome is called with null. That restores native system bar background rather than aligning it to the effective Shell toolbar background, which makes Shell inconsistent with ShellTabLayoutAppearanceTracker (and the intended default-color behavior).
			shellToolbar.BarTextColor = title ?? ShellRenderer.DefaultTitleColor;
			shellToolbar.BarBackground = background ?? new SolidColorBrush(ShellRenderer.DefaultBackgroundColor);
			shellToolbar.IconColor = foreground ?? ShellRenderer.DefaultForegroundColor;
			AndroidSystemChrome.UpdateTopChrome(
				toolbar,
				background is null ? null : shellToolbar.BarBackground);

src/Core/src/Platform/Android/WindowExtensions.cs:201

  • RestoreNavigationBarColor uses TypedValue.Data directly after ResolveAttribute(ColorBackground, ...). If the resolved attribute is a resource reference (common for themes), Data may be a resource ID rather than an ARGB color int, leading to an incorrect navigation bar color. This should validate typedValue.Type (color-int) and/or resolve typedValue.ResourceId via the context.
				if (_navigationBarColor == 0 && window.Context?.Theme is { } theme)
				{
					var typedValue = new TypedValue();
					if (theme.ResolveAttribute(global::Android.Resource.Attribute.ColorBackground, typedValue, true)
						&& typedValue.Data != 0)
					{
						restoreColor = new AColor(typedValue.Data);
					}
				}
  • Files reviewed: 17/18 changed files
  • Comments generated: 0 new

@kubaflo

kubaflo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Third empirical Android validation — updated head ed2c18786e4c6fe2b091d9c8096923bf34808c24

Fresh end-to-end Sandbox run against the current PR head, repeating the complete run‑2 matrix so the numbers are directly comparable. Verdict: PASS. All three run‑2 failures are fixed and none of the previously passing behavior regressed.

Head / change summary

Validated SHA ed2c18786e4c6fe2b091d9c8096923bf34808c24
Previous run SHA 607caf7db1f863f25b50270bfb2dcad8bd8f0ee2 (run 2 comment)
First run SHA 2f0d15b386e0 (run 1 comment)

Commits added since 607caf7db1f:

  • 09a533c483a — Fix initial Android bottom system chrome update
  • ed2c18786e4 — Strengthen initial bottom chrome regression coverage

Diff since 607caf7db1f — 2 files, +101/−1:

  • src/Controls/src/Core/Platform/Android/AndroidSystemChrome.cs (+62/−1)
  • src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Android.cs (+40)

The functional change adds a PendingBottomChromeUpdate queue: when UpdateBottomChrome runs while the chrome view has no resolvable Activity window and is not yet attached (window is null && !chromeView.IsAttachedToWindow), the brush is stored and re-applied via view.OnLoaded(...), with cancellation + ConditionalWeakTable removal once a real window resolves. That is precisely the code path behind all three run‑2 failures, which were all initial bottom-edge renders.

Environment

Device Android emulator emulator-5554, AVD Pixel_9_API_36_-_screenshots
Model / ABI sdk_gphone64_arm64 / arm64-v8a
OS / API Android 16 / API 36
Display 1080×2424 @ 420 dpi
TFM net11.0-android (base branch net11.0)
Config Debug, MauiAndroidSystemBarsUseMauiChrome=true (explicit opt-in)
Harness Sandbox app + Appium UIAutomator2, rendered-pixel sampling of the top/bottom system-bar bands

Core 17-check matrix — numerically comparable with run 2

Sampling: top band = 4.5% of height, bottom band = 1.8% of height, middle 75% of width, 3px stride. A check matches when ≥55% of sampled pixels are within tolerance or mean color distance ≤ 0.8×tolerance.

# Scenario Check Edge Expected matchRatio avgDist Run 2 Run 3
1 NavigationPage initial solid Top #FF8C00 97.4% 4.3
2 NavigationPage live update Top #1E88E5 97.4% 4.0
3 NavigationPage vertical gradient top edge Top #FF006E 97.3% 38.1
4 Top TabbedPage initial solid Top #FF8C00 97.4% 4.3
5 Top TabbedPage gradient Top #FF006E 94.7% 40.8
6 Top TabbedPage gradient→solid restoration Top #1E88E5 97.4% 4.0
7 Bottom TabbedPage initial (nav-bar edge) Bottom #2E7D32 93.0% 20.6
8 Bottom TabbedPage live update Bottom #6A1B9A 93.0% 18.7
9 Modal NavigationPage initial color Top #90EE90 97.4% 4.6
10 Modal NavigationPage live update Top #1E88E5 97.5% 4.0
11 Modal NavigationPage Activity restored after dismissal Top #FF8C00 97.4% 4.3
12 Shell explicit toolbar initial Top #FF8C00 97.4% 4.3
13 Shell explicit tab bar initial Bottom #00897B 93.0% 20.0
14 Shell explicit toolbar live update Top #1E88E5 97.5% 4.0
15 Shell explicit tab bar live update Bottom #2E7D32 93.0% 20.6
16 Shell defaults effective default toolbar Top #2C3E50 97.2% 8.3
17 Shell defaults effective default nav bar Bottom #FFFFFF 93.0% 18.5

Core: 17/17 matched, 0 mismatched (run 2: 14/17).
Comparable-13 subset (run‑1 parity): 13/13 matched, 0 mismatched (run 2: 11/13; run 1: 13/13).

Modal isolation (check 9–11) additionally asserted from device logs: status=True nav=True; separateWindow=True separateRoot=True — the modal uses a distinct dialog window/root and inherits icon-appearance flags. Shell mixed icon flags asserted as lightStatus=False lightNav=True.

Explicit status of each run‑2 failure

Run‑2 failure Run 2 (607caf7db1f) Run 3 (ed2c18786e4) Status
Bottom TabbedPage initial Green rendered orange-toned 0.0% match, avgDist 166.6 93.0% match, avgDist 20.6 Fixed
Shell initial Teal rendered orange-toned 0.0% match, avgDist 221.8 93.0% match, avgDist 20.0 Fixed
Shell effective-default White nav bar rendered orange 0.0% match, avgDist 231.8 93.0% match, avgDist 18.5 Fixed

Bottom TabbedPage initial Green

Shell initial Teal tab bar

Shell effective-default White nav bar

Added timing / lifecycle coverage for the new commits — 12/12 matched

Because the new commits defer the bottom update to OnLoaded, I added targeted cold-start, repeat and settle cases beyond the comparable matrix.

# Case Edge Expected matchRatio avgDist Result
E1 Repeat #2 — bottom tabs initial Bottom #2E7D32 93.0% 20.6
E2 Repeat #2 — Shell Teal initial Bottom #00897B 93.0% 20.0
E3 Repeat #2 — Shell defaults initial Bottom #FFFFFF 93.0% 18.5
E4 Settle probe 700 ms — bottom tabs Bottom #2E7D32 93.0% 20.6
E5 Settle probe 2500 ms — bottom tabs Bottom #2E7D32 93.0% 20.6
E6 Settle probe 700 ms — Shell Teal Bottom #00897B 93.0% 20.0
E7 Settle probe 2500 ms — Shell Teal Bottom #00897B 93.0% 20.0
E8 Cold start into bottom TabbedPage root Bottom #2E7D32 93.0% 20.6
E9 Cold start into Shell root (tab bar) Bottom #00897B 93.0% 20.0
E10 Cold start into Shell root (toolbar) Top #FF8C00 97.4% 4.3
E11 Cold start into Shell defaults root Bottom #FFFFFF 93.0% 18.5
E12 Cold start #2 into bottom TabbedPage root Bottom #2E7D32 93.0% 20.6

Cold start = terminateApp + activateApp, with the app booting directly into the scenario as the window root (persisted startup preference), so the bottom chrome view is created before attachment — the exact deferred path the fix introduces.

Grand total: 29/29 matched, 0 mismatched.

Repeatability & timing

  • The primary regression (bottom TabbedPage initial Green) was measured in the opt-in run — core, in-session repeat, 2 settle probes, and 2 cold starts — matching every time at an identical 93.0% / avgDist 20.6.
  • Shell Teal initial measured , Shell defaults nav bar ; all identical.
  • Settle probes show no timing dependence: the 700 ms and 2500 ms samples are numerically identical (20.6 / 20.0), so the bar is already correct at first paint rather than converging late.
  • The full 17-check matrix was executed twice end-to-end on this SHA (two independent app sessions); both produced core 17/17 and comparable 13/13.
Cold-start screenshots

Cold start bottom tabs
Cold start bottom tabs repeat
Cold start Shell teal
Cold start Shell defaults
Settle 700ms bottom tabs

Control

A fresh control was captured on this same SHA with MauiAndroidSystemBarsUseMauiChrome removed from the Sandbox project (property-omitted / legacy default). This is not a historical base checkout — same PR code, opt-in disabled — so it isolates the feature flag rather than the commit.

  • Control core: 2/17 matched, 15 mismatched
  • Control comparable-13: 0/13 matched, 13 mismatched (mirrors run 1's 13/13 legacy mismatch)
  • Control grand: 4/29 matched, 25 mismatched
  • Representative legacy distances: NavigationPage Orange avgDist 239.4, gradient 222.7, Blue 168.4 (vs 4.3 / 38.1 / 4.0 opt-in)

The 2 control "matches" are the Shell effective-default checks (#2C3E50 top, #FFFFFF bottom) — those expected values coincide with the legacy theme-owned colors, so they are non-discriminating in the control. They were still meaningful as regression checks, since run 2 rendered the same bottom bar orange.

Videos

Explicit opt-in, full interactive run on ed2c18786e4 (169 s):

pr35463_ed2c1878_explicit-opt-in-confirmation.mp4

Property-omitted legacy control on ed2c18786e4 (162 s):

pr35463_ed2c1878_default-omitted-control.mp4

Caveats

  • Single device: API 36 emulator only. Not re-verified on API ≤ 34 (pre-edge-to-edge, where StatusBarColor/NavigationBarColor are still honored) nor on physical hardware, 3-button navigation, landscape, or dark theme.
  • On API 36, Window.StatusBarColor / NavigationBarColor read back as #00000000 for every scenario (edge-to-edge; the setters are deprecated no-ops). Rendered-pixel sampling is therefore the authoritative signal here, not those properties.
  • Bottom-edge checks plateau at 93.0% rather than ~97% because the gesture-navigation pill occupies part of the sampled band. It is a constant offset present in passing and failing runs alike, and does not affect match/mismatch classification.
  • Gradient checks legitimately carry a higher avgDist (38–41) since the band spans a color ramp; tolerance is widened accordingly (70–75).
  • Verification is color/appearance only — no assertions on inset handling, contrast/accessibility, or MauiAndroidSystemBarsUseMauiChrome=false explicitly set (as opposed to omitted).
  • Sandbox harness changes are local to my validation worktree and are not part of this PR.

Verdict

PASS on ed2c18786e4c6fe2b091d9c8096923bf34808c24.

Core 17/17, comparable-13 13/13, timing/lifecycle 12/12, grand 29/29 — versus 14/17 and 11/13 on 607caf7db1f. All three previously reported failures are resolved, no previously passing check regressed, and the fresh legacy control still reproduces the opt-out behavior (0/13 comparable).

Automated Sandbox validation on a real Android emulator. Run 3 of 3.

@kubaflo
kubaflo merged commit 73f7cc1 into net11.0 Aug 7, 2026
30 checks passed
@jfversluis

Copy link
Copy Markdown
Member Author

@AlleSchonWeg FYI this was merged, a little different than originally planned, but this should set us up for a safe path to make this amazing.

Unfortunately, despite what you hoped for, this is for .NET 11. It will appear in RC1, so please test it out and let us know. We had to add it to .NET 11 because we need to add new APIs one way or the other. And new APIs are only allowed in major versions. As also already discussed, this is a major breaking (functional) change. Something that people usually don't like to be surprised with. Which brings me to the next thing.

We made this an opt-in feature for now. Mainly for 2 reasons. First, already mentioned, people don't like to be surprised with these things so opt-in mean people will make a conscious choice to do this. Second, because it's so late in the .NET 11 cycle I'm not sure if we have seen all the edge-cases for this. This way, we can ensure that the current behavior is preserved, no one should be broken, but the people interested in this can still use it. Additionally, if there are any bugs still in here, we now have both the ability to fix it and you as a user will have the option to fallback to a working state while we look into the bugfixes needed.

Thank you for your understanding, I do love this functionality and I think this really is a good change and will take away confusion especially for people new to MAUI, but it's also a drastic change that we have to consider carefully. Thank you for pushing for this and please try it out and let us know the results and what else we should look into for this according to you.

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

Labels

partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants