Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ public void SafeAreaMainGridSequentialButtonTesting()
Assert.That(finalAllPosition.Y, Is.EqualTo(allPosition.Y), "Final All position should match initial All position");
}

#if TEST_FAILS_ON_ANDROID
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaPerEdgeValidation()
Expand Down Expand Up @@ -128,6 +127,5 @@ public void SafeAreaPerEdgeValidation()
Assert.That(containerPositionWithoutSoftInput.Height, Is.EqualTo(containerPosition.Height), "ContentGrid height should return to original when Soft Input is dismissed with Container edges");
});
}
#endif
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue28986_ContentPage : _IssuesUITest
{
public override string Issue => "Test SafeArea ContentPage for per-edge safe area control";
public override string Issue => "Test SafeArea ContentPage for per-edge safe area control";

public Issue28986_ContentPage(TestDevice device) : base(device)
{
}
public Issue28986_ContentPage(TestDevice device) : base(device)
{
}

[Test]
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaMainGridBasicFunctionality()
{
Expand Down Expand Up @@ -97,8 +97,6 @@ public void SafeAreaMainGridSequentialButtonTesting()
Assert.That(finalAllPosition.Y, Is.EqualTo(allPosition.Y), "Final All position should match initial All position");
}


#if TEST_FAILS_ON_ANDROID
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaPerEdgeValidation()
Expand Down Expand Up @@ -129,6 +127,5 @@ public void SafeAreaPerEdgeValidation()
Assert.That(containerPositionWithoutSoftInput.Height, Is.EqualTo(containerPosition.Height), "ContentGrid height should return to original when Soft Input is dismissed with Container edges");
});
}
#endif
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ public void SafeAreaBorderMultipleOrientationChanges()
"Dimensions should still differ between portrait and landscape after multiple changes");
}

#if TEST_FAILS_ON_ANDROID
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaBorderSoftInputBehavior()
Expand Down Expand Up @@ -320,116 +319,5 @@ public void SafeAreaBorderSoftInputBehavior()
Assert.That(borderHeightChanged || safeAreaChanged, Is.True,
"Test should demonstrate that keyboard interaction affects either safe area or border layout");
}
#endif

#if TEST_FAILS_ON_ANDROID // Landscape orientation causes keyboard to occupy fullview
[Test]
[Category(UITestCategories.SafeAreaEdges)]
public void SafeAreaBorderSoftInputWithOrientationChange()
Copy link
Contributor

Choose a reason for hiding this comment

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

Why remove it? Still have sense to validate orientation changes, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jsuarezruiz , In Android Once the orientation is changed to Landscape, the input control and the keyboard occupies the entire screen, this will cause failure in the Rect lookup. The portrait soft input scenarios are already handled in other test files (SafeAreaPerEdgeValidation).
image

{
var borderContent = App.WaitForElement("BorderContent");

// 1. Set bottom edge to SoftInput mode for keyboard behavior
App.Tap("SetBottomSoftInputButton");

// Wait for the test status to confirm the change
var softInputSet = App.WaitForTextToBePresentInElement("TestStatus", "SoftInput", TimeSpan.FromSeconds(3));
Assert.That(softInputSet, Is.True, "Bottom edge should be set to SoftInput mode");

// 2. Show keyboard in portrait mode
App.Tap("TestEntry");

Thread.Sleep(1000);

Assert.That(App.IsKeyboardShown(), Is.True, "Keyboard should become visible in portrait mode");

// 3. Record portrait state with keyboard visible
var portraitWithKeyboardBounds = borderContent.GetRect();
var portraitWithKeyboardSafeArea = App.WaitForElement("SafeAreaInsets").GetText();

Assert.That(App.IsKeyboardShown(), Is.True, "IsKeyboardShown should confirm keyboard is visible");

// Verify we start in portrait with keyboard
Assert.That(portraitWithKeyboardBounds.Height, Is.GreaterThan(portraitWithKeyboardBounds.Width * 0.6),
"Should be in portrait orientation even with keyboard visible");

// 4. Change orientation to landscape while keyboard is still visible
App.SetOrientationLandscape();
Thread.Sleep(2000); // Wait for orientation change and layout to settle

// 5. Record landscape state with keyboard visible
var landscapeWithKeyboardBounds = borderContent.GetRect();
var landscapeWithKeyboardSafeArea = App.WaitForElement("SafeAreaInsets").GetText();

Assert.That(App.IsKeyboardShown(), Is.True, "Keyboard should remain visible after orientation change");

// Verify orientation changed to landscape
Assert.That(landscapeWithKeyboardBounds.Width, Is.GreaterThan(landscapeWithKeyboardBounds.Height * 0.6),
"Should now be in landscape orientation with keyboard visible");

// 6. Verify border maintains visibility in landscape with keyboard
Assert.That(landscapeWithKeyboardBounds.Width, Is.GreaterThan(0),
"Border should remain visible in landscape with keyboard");
Assert.That(landscapeWithKeyboardBounds.Height, Is.GreaterThan(0),
"Border should remain visible in landscape with keyboard");

// 7. Verify dimensions differ between orientations even with keyboard visible
Assert.That(portraitWithKeyboardBounds.Width, Is.Not.EqualTo(landscapeWithKeyboardBounds.Width).Within(10),
"Border width should differ between portrait and landscape with keyboard visible");
Assert.That(portraitWithKeyboardBounds.Height, Is.Not.EqualTo(landscapeWithKeyboardBounds.Height).Within(10),
"Border height should differ between portrait and landscape with keyboard visible");

// 8. Verify landscape has wider aspect ratio even with keyboard
var portraitAspectRatio = portraitWithKeyboardBounds.Height / portraitWithKeyboardBounds.Width;
var landscapeAspectRatio = landscapeWithKeyboardBounds.Height / landscapeWithKeyboardBounds.Width;

Assert.That(portraitAspectRatio, Is.GreaterThan(landscapeAspectRatio),
"Portrait should still have higher aspect ratio than landscape, even with keyboard visible");

// 9. Verify safe area insets adapted to landscape with keyboard
Assert.That(portraitWithKeyboardSafeArea, Is.Not.EqualTo(landscapeWithKeyboardSafeArea),
"Safe area insets should be different between portrait and landscape with keyboard");

// 10. Hide keyboard in landscape mode using proper dismiss method
App.DismissKeyboard();
Thread.Sleep(1000);

Assert.That(App.IsKeyboardShown(), Is.False, "Keyboard should be hidden in landscape mode");

// 11. Record landscape state with keyboard hidden
var landscapeWithoutKeyboardBounds = borderContent.GetRect();

Assert.That(App.IsKeyboardShown(), Is.False, "IsKeyboardShown should confirm keyboard is hidden");

// 12. Verify border expanded when keyboard was dismissed in landscape
Assert.That(landscapeWithoutKeyboardBounds.Height, Is.GreaterThan(landscapeWithKeyboardBounds.Height),
"Border should expand when keyboard is hidden in landscape mode");

// 13. Change back to portrait without keyboard
App.SetOrientationPortrait();
Thread.Sleep(2000); // Wait for orientation change and layout to settle

// 14. Record final portrait state without keyboard
var finalPortraitBounds = borderContent.GetRect();

// Verify back in portrait
Assert.That(finalPortraitBounds.Height, Is.GreaterThan(finalPortraitBounds.Width * 0.8),
"Should be back in portrait orientation");

// 15. Verify overall stability - border should be functional in final state
Assert.That(finalPortraitBounds.Width, Is.GreaterThan(0),
"Border should be visible in final portrait state");
Assert.That(finalPortraitBounds.Height, Is.GreaterThan(0),
"Border should be visible in final portrait state");

// 16. Verify no errors occurred during complex interaction
var testStatus = App.WaitForElement("TestStatus");
Assert.That(testStatus.GetText(), Does.Not.Contain("Error"),
"No errors should occur during orientation change with keyboard interaction");

// 17. Verify final state is stable (keyboard hidden)
Assert.That(App.IsKeyboardShown(), Is.False, "Keyboard should remain hidden in final state");
}
#endif
}
#endif
49 changes: 18 additions & 31 deletions src/Core/src/Platform/Android/SafeAreaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,6 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
var right = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(2, layout), baseSafeArea.Right, 2, isKeyboardShowing, keyboardInsets);
var bottom = GetSafeAreaForEdge(GetSafeAreaRegionForEdge(3, layout), baseSafeArea.Bottom, 3, isKeyboardShowing, keyboardInsets);

if (isKeyboardShowing &&
Copy link
Contributor

Choose a reason for hiding this comment

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

How does the new logic handle AdjustPan mode correctly without this check?, no longer necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jsuarezruiz , The new approach uses SafeAreaRegions flags to determine if SoftInput should be handled, making it more flexible and declarative rather than checking the window's SoftInput mode directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When SafeAreaRegions.SoftInput is not set for the bottom edge, GetSafeAreaForEdge will return the original safe area or 0, effectively achieving the same result as the AdjustPan check but in a more systematic way.

context.GetActivity()?.Window is Window window &&
window?.Attributes is WindowManagerLayoutParams attr)
{
// If the window is panned from the keyboard being open
// and there isn't a bottom inset to apply then just don't touch anything
var softInputMode = attr.SoftInputMode;
if (softInputMode == SoftInput.AdjustPan
&& bottom == 0
)
{
return WindowInsetsCompat.Consumed;
}
}

var globalWindowInsetsListener = context.GetGlobalWindowInsetListener();
bool hasTrackedViews = globalWindowInsetsListener?.HasTrackedView == true;
Expand Down Expand Up @@ -237,7 +223,7 @@ internal static SafeAreaRegions GetSafeAreaRegionForEdge(int edge, ICrossPlatfor
}
else
{
newWindowInsets = windowInsets;
newWindowInsets = windowInsets;
}

// Fallback: return the base safe area for legacy views
Expand All @@ -248,27 +234,28 @@ internal static double GetSafeAreaForEdge(SafeAreaRegions safeAreaRegion, double
{
// Edge-to-edge content - no safe area padding
if (safeAreaRegion == SafeAreaRegions.None)
{
return 0;
}

// Handle SoftInput specifically - only apply keyboard insets for bottom edge when keyboard is showing
if (isKeyboardShowing && edge == 3)
// Handle SoftInput specifically for bottom edge when keyboard is showing
if (edge == 3 && SafeAreaEdges.IsSoftInput(safeAreaRegion))
{
if (SafeAreaEdges.IsSoftInput(safeAreaRegion))
return keyBoardInsets.Bottom;

// if they keyboard is showing then we will just return 0 for the bottom inset
// because that part of the view is covered by the keyboard so we don't want to pad the view
return 0;
return HandleSoftInputRegion(safeAreaRegion, originalSafeArea, isKeyboardShowing, keyBoardInsets);
}

// All other regions respect safe area in some form
// This includes:
// - Default: Platform default behavior
// - All: Obey all safe area insets
// - Container: Content flows under keyboard but stays out of bars/notch
// - Any combination of the above flags
// All other regions respect safe area (Default, All, Container, etc.)
return originalSafeArea;
}

static double HandleSoftInputRegion(SafeAreaRegions safeAreaRegion, double originalSafeArea, bool isKeyboardShowing, SafeAreaPadding keyBoardInsets)
{
if (!isKeyboardShowing)
{
// When keyboard is hidden, only apply safe area for "All" regions
return safeAreaRegion == SafeAreaRegions.All ? originalSafeArea : 0;
Comment on lines +253 to +254
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The logic for when the keyboard is hidden appears incorrect. When safeAreaRegion is SafeAreaRegions.SoftInput and the keyboard is hidden, this returns 0. However, according to the previous logic (line 242 in the old code), when the keyboard is not showing for bottom edge SoftInput, it should return 0, which matches. But this new implementation doesn't account for other safe area region combinations that include SoftInput flag. The check safeAreaRegion == SafeAreaRegions.All is too restrictive - it won't apply safe area for combinations like SafeAreaRegions.All | SafeAreaRegions.SoftInput or other valid flag combinations. Consider using SafeAreaEdges.HasFlag pattern or checking if the region includes the All flag: return (safeAreaRegion & SafeAreaRegions.All) != 0 ? originalSafeArea : 0;

Suggested change
// When keyboard is hidden, only apply safe area for "All" regions
return safeAreaRegion == SafeAreaRegions.All ? originalSafeArea : 0;
// When keyboard is hidden, only apply safe area for regions that include "All"
return (safeAreaRegion & SafeAreaRegions.All) != 0 ? originalSafeArea : 0;

Copilot uses AI. Check for mistakes.
}

// When keyboard is showing, use the larger of keyboard inset or original safe area
var keyboardInset = keyBoardInsets.Bottom;
return Math.Max(keyboardInset, originalSafeArea);
Comment on lines +257 to +259
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

This logic unconditionally returns the maximum of keyboard inset and original safe area for all SoftInput regions when the keyboard is showing, but this doesn't align with the original behavior. The previous code (line 242) returned keyBoardInsets.Bottom directly when SafeAreaRegion was SoftInput and keyboard was showing. Using Math.Max will always apply at least the original safe area padding even when SoftInput is set alone (without All), which changes the behavior. For SafeAreaRegions.SoftInput only (without All), it should return just the keyboard inset, not the maximum. Consider checking if All flag is present: return (safeAreaRegion & SafeAreaRegions.All) != 0 ? Math.Max(keyboardInset, originalSafeArea) : keyboardInset;

Suggested change
// When keyboard is showing, use the larger of keyboard inset or original safe area
var keyboardInset = keyBoardInsets.Bottom;
return Math.Max(keyboardInset, originalSafeArea);
// When keyboard is showing, use the larger of keyboard inset or original safe area only if "All" is present
var keyboardInset = keyBoardInsets.Bottom;
return (safeAreaRegion & SafeAreaRegions.All) != 0 ? Math.Max(keyboardInset, originalSafeArea) : keyboardInset;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Previously just returned keyboardInset. With floating/split keyboards, the keyboard inset might be smaller than the navigation bar safe area. Could verify?

}
}
Loading