diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue8680.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue8680.cs new file mode 100644 index 000000000000..0546cb2f3a12 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue8680.cs @@ -0,0 +1,66 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 8680, "Rework OnBackButtonPressed to use onBackPressedDispatcher", PlatformAffected.Android)] +public class Issue8680 : TestNavigationPage +{ + protected override void Init() + { + PushAsync(new Issue8680MainPage()); + } +} + +public class Issue8680MainPage : ContentPage +{ + public Issue8680MainPage() + { + var navigateButton = new Button + { + Text = "Go to Intercept Page", + AutomationId = "NavigateButton", + }; + navigateButton.Clicked += async (s, e) => + { + await Navigation.PushAsync(new Issue8680InterceptPage()); + }; + + Content = new VerticalStackLayout + { + Children = + { + new Label { Text = "Main Page", AutomationId = "MainPageLabel" }, + navigateButton, + } + }; + } +} + +public class Issue8680InterceptPage : ContentPage +{ + int _backPressCount; + readonly Label _statusLabel; + + public Issue8680InterceptPage() + { + _statusLabel = new Label + { + Text = "Back not pressed yet", + AutomationId = "StatusLabel", + }; + + Content = new VerticalStackLayout + { + Children = + { + _statusLabel, + new Label { Text = "Press device back button — it should be intercepted", AutomationId = "InterceptPageLabel" }, + } + }; + } + + protected override bool OnBackButtonPressed() + { + _backPressCount++; + _statusLabel.Text = $"Back intercepted: {_backPressCount}"; + return true; // true = handled, prevents navigation back + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8680.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8680.cs new file mode 100644 index 000000000000..d53179569ca7 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8680.cs @@ -0,0 +1,39 @@ +#if ANDROID // Android-specific: OnBackButtonPressed uses onBackPressedDispatcher +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue8680 : _IssuesUITest +{ + public Issue8680(TestDevice device) : base(device) { } + + public override string Issue => "Rework OnBackButtonPressed to use onBackPressedDispatcher"; + + [Test] + [Category(UITestCategories.Navigation)] + public void BackButtonPressIsInterceptedByOnBackButtonPressed() + { + // Navigate to the intercept page + App.WaitForElement("NavigateButton"); + App.Tap("NavigateButton"); + + // Confirm we are on the intercept page + App.WaitForElement("InterceptPageLabel"); + + // Press the device back button — should be intercepted (page stays) + App.Back(); + + // The page should still be visible because OnBackButtonPressed returned true + App.WaitForElement("StatusLabel"); + Assert.That( + App.FindElement("StatusLabel").GetText(), + Is.EqualTo("Back intercepted: 1"), + "OnBackButtonPressed should have been called exactly once per back press (detects dual-fire regression on API 33+)."); + + // The intercept page should still be displayed (not popped) + App.WaitForElement("InterceptPageLabel"); + } +} +#endif \ No newline at end of file diff --git a/src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs b/src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs index 1f813c41cd3a..7a797df1d036 100644 --- a/src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs +++ b/src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs @@ -1,11 +1,9 @@ -using System; using Android.App; using Android.Content; using Android.Content.PM; using Android.Content.Res; using Android.OS; using Android.Views; -using Microsoft.Maui.Devices; using Microsoft.Maui.LifecycleEvents; namespace Microsoft.Maui @@ -20,16 +18,6 @@ protected override void OnActivityResult(int requestCode, Result resultCode, Int IPlatformApplication.Current?.Services?.InvokeLifecycleEvents(del => del(this, requestCode, resultCode, data)); } - // TODO: Investigate whether the new AndroidX way is actually useful: - // https://developer.android.com/reference/android/app/Activity#onBackPressed() - [Obsolete] -#pragma warning disable 809 - public override void OnBackPressed() -#pragma warning restore 809 - { - HandleBackNavigation(); - } - public override void OnConfigurationChanged(Configuration newConfig) { base.OnConfigurationChanged(newConfig); @@ -136,10 +124,12 @@ public override bool OnKeyUp(Keycode keyCode, KeyEvent? e) } /// - /// Central handler used by both legacy and the Android 13+ predictive back gesture callback. - /// Implements lifecycle event invocation and default back stack propagation unless explicitly prevented. + /// Central handler invoked by when the back button is pressed. + /// This method only checks whether MAUI lifecycle handlers intercept the back press. + /// Callers are responsible for invoking as a + /// fallback when this method returns . /// - void HandleBackNavigation() + internal void HandleBackNavigation() { var preventBackPropagation = false; IPlatformApplication.Current?.Services?.InvokeLifecycleEvents(del => diff --git a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs index de7ae83171c8..62b9b4f72ca4 100644 --- a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs +++ b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs @@ -1,4 +1,3 @@ -using System; using Android.OS; using Android.Views; using AndroidX.Activity;