diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 21d6336b2c..4c82a175fc 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -7,6 +7,8 @@ using Android.OS;
using osu.Framework.Allocation;
using osu.Game;
using osu.Game.Updater;
+using osu.Game.Utils;
+using Xamarin.Essentials;
namespace osu.Android
{
@@ -19,6 +21,7 @@ namespace osu.Android
: base(null)
{
gameActivity = activity;
+ powerStatus = new AndroidPowerStatus();
}
public override Version AssemblyVersion
@@ -72,5 +75,11 @@ namespace osu.Android
}
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
+ public class AndroidPowerStatus : PowerStatus
+ {
+ public override double ChargeLevel => Battery.ChargeLevel;
+
+ public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
+ }
}
}
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index 54857ac87d..64d5e5b1c8 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -63,5 +63,9 @@
5.0.0
+
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 0c21c75290..b6c57b219d 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -21,6 +21,8 @@ using osu.Game.Updater;
using osu.Desktop.Windows;
using osu.Framework.Threading;
using osu.Game.IO;
+using osu.Game.Utils;
+using osu.Framework.Allocation;
namespace osu.Desktop
{
@@ -33,6 +35,7 @@ namespace osu.Desktop
: base(args)
{
noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
+ powerStatus = new DefaultPowerStatus();
}
public override StableStorage GetStorageForStableInstall()
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index a31d53e3b6..c5cc10993c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -25,6 +25,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
+using osu.Game.Utils;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private readonly VolumeOverlay volumeOverlay;
+ [Resolved]
+ private PowerStatus powerStatus { get; set; }
+
private readonly ChangelogOverlay changelogOverlay;
public TestScenePlayerLoader()
@@ -93,26 +97,6 @@ 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()
{
@@ -290,32 +274,6 @@ 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)
@@ -334,6 +292,30 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
+ [TestCase(false, 1.0)] // not charging, full battery --> no warning
+ [TestCase(false, 0.2)] // not charging, at cutoff --> warning
+ [TestCase(false, 0.1)] // charging, below cutoff --> warning
+ public void TestLowBatteryNotification(bool isCharging, double chargeLevel)
+ {
+ AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false);
+
+ // set charge status and level
+ AddStep("load player", () => resetPlayer(false, () => { powerStatus.IsCharging = isCharging; powerStatus.ChargeLevel = chargeLevel; }));
+ AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
+ int notificationCount = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 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);
+ }
+
[Test]
public void TestEpilepsyWarningEarlyExit()
{
@@ -356,8 +338,6 @@ 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/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 21313d0596..53873b0127 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -40,6 +40,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Skinning;
+using osu.Game.Utils;
using osuTK.Input;
using RuntimeInfo = osu.Framework.RuntimeInfo;
@@ -95,6 +96,8 @@ namespace osu.Game
protected Storage Storage { get; set; }
+ protected PowerStatus powerStatus;
+
[Cached]
[Cached(typeof(IBindable))]
protected readonly Bindable Ruleset = new Bindable();
@@ -329,6 +332,8 @@ namespace osu.Game
dependencies.CacheAs(MusicController);
Ruleset.BindValueChanged(onRulesetChanged);
+
+ dependencies.CacheAs(powerStatus);
}
private void onRulesetChanged(ValueChangedEvent r)
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 01a51e6e7a..9710fa7e46 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -24,9 +24,9 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Users;
+using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
-using Xamarin.Essentials;
namespace osu.Game.Screens.Play
{
@@ -113,6 +113,9 @@ namespace osu.Game.Screens.Play
[Resolved]
private AudioManager audioManager { get; set; }
+ [Resolved]
+ private PowerStatus powerStatus { get; set; }
+
public PlayerLoader(Func createPlayer)
{
this.createPlayer = createPlayer;
@@ -265,7 +268,6 @@ namespace osu.Game.Screens.Play
}
#endregion
-
protected override void Update()
{
base.Update();
@@ -477,45 +479,18 @@ namespace osu.Game.Screens.Play
#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)
+ // Checks if the notification has not been shown yet, device is unplugged and if device battery is at or below the cutoff
+ if (!powerStatus.IsCharging && powerStatus.ChargeLevel <= powerStatus.BatteryCutoff)
{
- 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
{
diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs
index 71b0b02fa6..afce7dd38b 100644
--- a/osu.Game/Tests/OsuTestBrowser.cs
+++ b/osu.Game/Tests/OsuTestBrowser.cs
@@ -7,11 +7,16 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Screens.Backgrounds;
+using osu.Game.Utils;
namespace osu.Game.Tests
{
public class OsuTestBrowser : OsuGameBase
{
+ public OsuTestBrowser()
+ {
+ powerStatus = new DefaultPowerStatus();
+ }
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs
new file mode 100644
index 0000000000..0657c98af1
--- /dev/null
+++ b/osu.Game/Utils/PowerStatus.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Utils
+{
+ public abstract class PowerStatus
+ {
+ ///
+ /// The maximum battery level before a warning notification
+ /// is sent.
+ ///
+ public virtual double BatteryCutoff { get; } = 0.2;
+ public virtual double ChargeLevel { get; set; }
+ public virtual bool IsCharging { get; set; }
+ }
+
+ public class DefaultPowerStatus : PowerStatus
+ {
+ public override double ChargeLevel { get; set; } = 1;
+ public override bool IsCharging { get; set; } = true;
+ }
+}
diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs
index 5125ad81e0..21dce338dc 100644
--- a/osu.iOS/OsuGameIOS.cs
+++ b/osu.iOS/OsuGameIOS.cs
@@ -5,13 +5,30 @@ using System;
using Foundation;
using osu.Game;
using osu.Game.Updater;
+using osu.Game.Utils;
+using Xamarin.Essentials;
namespace osu.iOS
{
public class OsuGameIOS : OsuGame
{
+ public OsuGameIOS()
+ : base(null)
+ {
+ powerStatus = new IOSPowerStatus();
+ }
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
+
+ public class IOSPowerStatus : PowerStatus
+ {
+ // The low battery alert appears at 20% on iOS
+ // https://github.com/ppy/osu/issues/12239
+ public override double BatteryCutoff => 0.25;
+ public override double ChargeLevel => Battery.ChargeLevel;
+
+ public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
+ }
}
}
diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj
index 1e9a21865d..ed6f52c60e 100644
--- a/osu.iOS/osu.iOS.csproj
+++ b/osu.iOS/osu.iOS.csproj
@@ -116,5 +116,9 @@
false
+
+
+
+