Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to exit/minimize the app on Android when back is pressed #5044

Merged
merged 8 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
16 changes: 14 additions & 2 deletions osu.Framework.Android/AndroidGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,34 @@ protected override void OnRestart()
Bass.Start();
}

private bool allowExiting => gameView.Host?.AllowExitingAndroid.Result.Value ?? true;

public override void OnBackPressed()
{
// Avoid the default implementation that does close the app.
// This only happens when the back button could not be captured from OnKeyDown.
// This method is called only when the back button was not handled by OnKeyDown and OnKeyUp.
// Avoid the default implementation that closes the app. Return to the home screen instead.
if (allowExiting)
MoveTaskToBack(true);
}

// On some devices and keyboard combinations the OnKeyDown event does not propagate the key event to the view.
// Here it is done manually to ensure that the keys actually land in the view.

public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
if (keyCode == Keycode.Back && allowExiting)
// let the base handle it so we get the `OnBackPressed()` event.
return base.OnKeyDown(keyCode, e);

return gameView.OnKeyDown(keyCode, e);
}

public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
if (keyCode == Keycode.Back && allowExiting)
// let the base handle it so we get the `OnBackPressed()` event.
return base.OnKeyUp(keyCode, e);

return gameView.OnKeyUp(keyCode, e);
}

Expand Down
2 changes: 0 additions & 2 deletions osu.Framework.Tests.Android/TestGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.

using Android.App;
using Android.OS;
using Android.Views;
using osu.Framework.Android;

namespace osu.Framework.Tests.Android
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osuTK.Graphics;
using osuTK.Input;

namespace osu.Framework.Tests.Visual.Platform
{
public class TestSceneAllowExitingAndroid : FrameworkTestScene
{
[Resolved]
private GameHost host { get; set; }

private readonly BindableBool allowExit = new BindableBool(true);

public TestSceneAllowExitingAndroid()
{
Children = new Drawable[]
{
new ExitVisualiser
{
Width = 0.5f,
RelativeSizeAxes = Axes.Both,
},
new EscapeVisualizer
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
};
}

protected override void LoadComplete()
{
base.LoadComplete();
host.AllowExitingAndroid.AddSource(allowExit);
}

[Test]
public void TestToggleSuspension()
{
AddToggleStep("toggle allow exit", v => allowExit.Value = v);
}

protected override void Dispose(bool isDisposing)
{
host.AllowExitingAndroid.RemoveSource(allowExit);
base.Dispose(isDisposing);
}

private class ExitVisualiser : Box
{
private readonly IBindable<bool> allowExit = new Bindable<bool>();

[BackgroundDependencyLoader]
private void load(GameHost host)
{
allowExit.BindTo(host.AllowExitingAndroid.Result);
allowExit.BindValueChanged(v => Colour = v.NewValue ? Color4.Green : Color4.Red, true);
}
}

private class EscapeVisualizer : Box
{
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Key == Key.Escape)
this.FlashColour(Color4.Blue, 700, Easing.OutQuart);

return base.OnKeyDown(e);
}
}
}
}
9 changes: 9 additions & 0 deletions osu.Framework/Platform/GameHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ public abstract class GameHost : IIpcHost, IDisposable
/// </remarks>
public readonly AggregateBindable<bool> AllowScreenSuspension = new AggregateBindable<bool>((a, b) => a & b, new Bindable<bool>(true));

/// <summary>
/// Allow exiting the app on Android when the back button is pressed.
/// </summary>
/// <remarks>
/// If <c>true</c>, pressing back will return to the home screen (putting the app in background and suspending it).
/// If <c>false</c>, the back button will be treated as <see cref="InputKey.Escape"/>.
/// </remarks>
public readonly AggregateBindable<bool> AllowExitingAndroid = new AggregateBindable<bool>((a, b) => a & b, new Bindable<bool>(true));
Copy link
Member

Choose a reason for hiding this comment

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

Might be better to rename to AllowSuspendingAndroid (AllowSuspendToBackground without the android suffix maybe?) as it isn't really an exit operation. Not 100% on having android specific variables in here, but I guess it'll have to do for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Just checked and the Android docs seem to call it "stopping the activity" and "moving to background". So AllowSuspendToBackground could work. Maybe there's a better name that would encompass the "when back is pressed" part of this.

How about AllowNativeBackNavigation?

frenzibyte marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// For IPC messaging purposes, whether this <see cref="GameHost"/> is the primary (bound) host.
/// </summary>
Expand Down