diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml
index 770eaf2222..e717bab310 100644
--- a/osu.Android/Properties/AndroidManifest.xml
+++ b/osu.Android/Properties/AndroidManifest.xml
@@ -6,5 +6,6 @@
+
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 88fbf09ef4..a31d53e3b6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -93,6 +93,26 @@ namespace osu.Game.Tests.Visual.Gameplay
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
}
+ ///
+ /// Sets the input manager child to a new test player loader container instance with a custom battery level
+ ///
+ /// If the test player should behave like the production one.
+ /// If the player's device is plugged in.
+ /// A custom battery level for the test player.
+ private void resetPlayerWithBattery(bool interactive, bool pluggedIn, double batteryLevel)
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToTrack(Beatmap.Value.Track);
+
+ loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive));
+ loader.batteryManager.ChargeLevel = batteryLevel;
+ loader.batteryManager.PluggedIn = pluggedIn;
+ LoadScreen(loader);
+ }
+
[Test]
public void TestEarlyExitBeforePlayerConstruction()
{
@@ -270,6 +290,32 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for player load", () => player.IsLoaded);
}
+ [TestCase(false, 1.0)] // not plugged in, full battery, no notification
+ [TestCase(false, 0.2)] // not plugged in, at warning level, no notification
+ [TestCase(true, 0.1)] // plugged in, charging, below warning level, no notification
+ [TestCase(false, 0.1)] // not plugged in, below warning level, notification
+ public void TestLowBatteryNotification(bool pluggedIn, double batteryLevel)
+ {
+ AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false);
+
+ // mock phone on battery
+ AddStep("load player", () => resetPlayerWithBattery(false, pluggedIn, batteryLevel));
+ AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
+ int notificationCount = !pluggedIn && batteryLevel < PlayerLoader.battery_tolerance ? 1 : 0;
+ AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount);
+ AddStep("click notification", () =>
+ {
+ var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
+ var flowContainer = scrollContainer.Children.OfType>().First();
+ var notification = flowContainer.First();
+
+ InputManager.MoveMouseTo(notification);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("wait for player load", () => player.IsLoaded);
+ }
+
[TestCase(true)]
[TestCase(false)]
public void TestEpilepsyWarning(bool warning)
@@ -310,6 +356,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public IReadOnlyList DisplayedMods => MetadataInfo.Mods.Value;
+ public new BatteryManager batteryManager => base.batteryManager;
+
public TestPlayerLoader(Func createPlayer)
: base(createPlayer)
{
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 0e1f6f6b0c..df6d17f615 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index 36eb6964dd..e8ee04731c 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Configuration
{
SetDefault(Static.LoginOverlayDisplayed, false);
SetDefault(Static.MutedAudioNotificationShownOnce, false);
+ SetDefault(Static.BatteryLowNotificationShownOnce, false);
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
SetDefault(Static.SeasonalBackgrounds, null);
}
@@ -25,6 +26,7 @@ namespace osu.Game.Configuration
{
LoginOverlayDisplayed,
MutedAudioNotificationShownOnce,
+ BatteryLowNotificationShownOnce,
///
/// Info about seasonal backgrounds available fetched from API - see .
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 679b3c7313..01a51e6e7a 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -26,6 +26,7 @@ using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
+using Xamarin.Essentials;
namespace osu.Game.Screens.Play
{
@@ -121,6 +122,7 @@ namespace osu.Game.Screens.Play
private void load(SessionStatics sessionStatics)
{
muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce);
+ batteryWarningShownOnce = sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce);
InternalChild = (content = new LogoTrackingContainer
{
@@ -196,6 +198,7 @@ namespace osu.Game.Screens.Play
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
showMuteWarningIfNeeded();
+ showBatteryWarningIfNeeded();
}
public override void OnResuming(IScreen last)
@@ -470,5 +473,73 @@ namespace osu.Game.Screens.Play
}
#endregion
+
+ #region Low battery warning
+ private Bindable batteryWarningShownOnce;
+
+ // Send a warning if battery is less than 20%
+ public const double battery_tolerance = 0.2;
+
+ private void showBatteryWarningIfNeeded()
+ {
+ if (!batteryWarningShownOnce.Value)
+ {
+ // Checks if the notification has not been shown yet, device is unplugged and if device battery is low.
+ if (!batteryManager.PluggedIn && batteryManager.ChargeLevel < battery_tolerance)
+ {
+ Console.WriteLine("Battery level: {0}", batteryManager.ChargeLevel);
+ notificationOverlay?.Post(new BatteryWarningNotification());
+ batteryWarningShownOnce.Value = true;
+ }
+ }
+ }
+ public BatteryManager batteryManager = new BatteryManager();
+ public class BatteryManager
+ {
+ public double ChargeLevel;
+ public bool PluggedIn;
+ public BatteryManager()
+ {
+ // Attempt to get battery information using Xamarin.Essentials
+ // Xamarin.Essentials throws a NotSupportedOrImplementedException on .NET standard so this only works on iOS/Android
+ // https://github.com/xamarin/Essentials/blob/7218ab88f7fbe00ec3379bd54e6c0ce35ffc0c22/Xamarin.Essentials/Battery/Battery.netstandard.tvos.cs
+ try
+ {
+ ChargeLevel = Battery.ChargeLevel;
+ PluggedIn = Battery.PowerSource == BatteryPowerSource.Battery;
+ }
+ catch (NotImplementedException e)
+ {
+ Console.WriteLine("Could not access battery info: {0}", e);
+ ChargeLevel = 1.0;
+ PluggedIn = false;
+ }
+ }
+ }
+
+ private class BatteryWarningNotification : SimpleNotification
+ {
+ public override bool IsImportant => true;
+
+ public BatteryWarningNotification()
+ {
+ Text = "Your battery level is low! Charge your device to prevent interruptions.";
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, NotificationOverlay notificationOverlay)
+ {
+ Icon = FontAwesome.Solid.BatteryQuarter;
+ IconBackgound.Colour = colours.RedDark;
+
+ Activated = delegate
+ {
+ notificationOverlay.Hide();
+ return true;
+ };
+ }
+ }
+
+ #endregion
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 292e5b932f..c68be5313d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
Library
@@ -35,5 +35,6 @@
+