From 0a6baf670eff5a90704b95277cf8a8b89dd44375 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Wed, 7 Apr 2021 14:41:21 -0400 Subject: [PATCH] Send a warning notification if device is unplugged and low battery - Uses Xamarin.Essentials in osu.Game.PlayerLoader to check battery level - Encapsulated battery checking in the public BatteryManager class so battery level and plugged in status can be accessed and edited in TestPlayerLoader - When checking battery level, catch NotImplementedException thrown by Xamarin.Essentials.Battery on non-mobile platforms - Added visual unit tests for battery notification To mock battery status and level, we had to define a batteryManager object in TestPlayerLoader and add a new function ResetPlayerWithBattery() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Marlina José --- osu.Android/Properties/AndroidManifest.xml | 1 + .../Visual/Gameplay/TestScenePlayerLoader.cs | 48 +++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/Configuration/SessionStatics.cs | 2 + osu.Game/Screens/Play/PlayerLoader.cs | 71 +++++++++++++++++++ osu.Game/osu.Game.csproj | 3 +- 6 files changed, 125 insertions(+), 2 deletions(-) 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 @@ private void resetPlayer(bool interactive, Action beforeLoadAction = null) 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 @@ private void addVolumeSteps(string volumeName, Action beforeLoad, Func ass 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 @@ private class TestPlayerLoader : PlayerLoader 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 @@ protected override void InitialiseDefaults() { 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 @@ public enum Static { 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.Users; using osuTK; using osuTK.Graphics; +using Xamarin.Essentials; namespace osu.Game.Screens.Play { @@ -121,6 +122,7 @@ public PlayerLoader(Func createPlayer) private void load(SessionStatics sessionStatics) { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); + batteryWarningShownOnce = sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce); InternalChild = (content = new LogoTrackingContainer { @@ -196,6 +198,7 @@ public override void OnEntering(IScreen last) Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0)); showMuteWarningIfNeeded(); + showBatteryWarningIfNeeded(); } public override void OnResuming(IScreen last) @@ -470,5 +473,73 @@ private void load(OsuColour colours, AudioManager audioManager, NotificationOver } #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 @@ +