Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 8 additions & 3 deletions osu.Android/OsuGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class OsuGameActivity : AndroidGameActivity

public new bool IsTablet { get; private set; }

private readonly OsuGameAndroid game;
private OsuGameAndroid? game;

private bool gameCreated;

Expand All @@ -60,6 +60,9 @@ protected override Framework.Game CreateGame()
if (gameCreated)
throw new InvalidOperationException("Framework tried to create a game twice.");

if (game == null)
throw new InvalidOperationException("Game was not initialised.");

gameCreated = true;
return game;
}
Expand All @@ -80,6 +83,8 @@ protected override void OnCreate(Bundle? savedInstanceState)
// first use because the internal Platform.CurrentActivity is null.
Microsoft.Maui.ApplicationModel.Platform.Init(this, savedInstanceState);



// OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.
// on first launch we still have to fire manually.
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
Expand Down Expand Up @@ -145,7 +150,7 @@ private void handleIntent(Intent? intent)
else if (osu_url_schemes.Contains(intent.Scheme))
{
if (intent.DataString != null)
game.HandleLink(intent.DataString);
game?.HandleLink(intent.DataString);
}

break;
Expand Down Expand Up @@ -188,7 +193,7 @@ await Task.WhenAll(uris.Select(async uri =>
}
})).ConfigureAwait(false);

await game.Import(tasks.ToArray()).ConfigureAwait(false);
if (game != null) await game.Import(tasks.ToArray()).ConfigureAwait(false);
}, TaskCreationOptions.LongRunning);
}
}
91 changes: 65 additions & 26 deletions osu.Android/OsuGameAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,35 @@ public partial class OsuGameAndroid : OsuGame
[Cached]
private readonly OsuGameActivity gameActivity;

private readonly PackageInfo? packageInfo;
private readonly object packageInfoLock = new object();
private PackageInfo? packageInfo;
private bool packageInfoChecked;

private PackageInfo? getPackageInfo()
{
lock (packageInfoLock)
{
if (packageInfoChecked)
return packageInfo;

try
{
// Use the activity instance directly instead of Application.Context to ensure
// the PackageManager is accessible even on newer/stricter Android versions.
packageInfo = gameActivity.PackageManager?.GetPackageInfo(gameActivity.PackageName!, 0);
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] Failed to retrieve package info: {e.Message}");
}
finally
{
packageInfoChecked = true;
}

return packageInfo;
}
}

public override Vector2 ScalingContainerTargetDrawSize => DrawWidth > 0 && DrawHeight > 0
? new Vector2(1024, 1024 * DrawHeight / DrawWidth)
Expand All @@ -53,16 +81,6 @@ public OsuGameAndroid(OsuGameActivity activity)
: base(null)
{
gameActivity = activity;

try
{
packageInfo = Application.Context.ApplicationContext!.PackageManager!.GetPackageInfo(Application.Context.ApplicationContext.PackageName!, 0);
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] Failed to retrieve package info: {e.Message}");
packageInfo = null;
}
}

public override string Version
Expand All @@ -72,7 +90,7 @@ public override string Version
if (!IsDeployedBuild)
return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release");

return packageInfo?.VersionName ?? @"unknown";
return getPackageInfo()?.VersionName ?? @"unknown";
}
}

Expand All @@ -82,7 +100,7 @@ public override Version AssemblyVersion
{
try
{
string? versionName = packageInfo?.VersionName;
string? versionName = getPackageInfo()?.VersionName;

if (!string.IsNullOrEmpty(versionName))
return new Version(versionName.Split('-').First());
Expand All @@ -97,12 +115,12 @@ public override Version AssemblyVersion
}

[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load()
{
config.BindWith(OsuSetting.AndroidPerformanceMode, performanceMode);
config.BindWith(OsuSetting.AndroidLowLatencyAudio, lowLatencyAudio);
config.BindWith(OsuSetting.AndroidVulkanProbe, vulkanProbeEnabled);
config.BindWith(OsuSetting.AudioOffset, audioOffset);
LocalConfig.BindWith(OsuSetting.AndroidPerformanceMode, performanceMode);
LocalConfig.BindWith(OsuSetting.AndroidLowLatencyAudio, lowLatencyAudio);
LocalConfig.BindWith(OsuSetting.AndroidVulkanProbe, vulkanProbeEnabled);
LocalConfig.BindWith(OsuSetting.AudioOffset, audioOffset);
}

protected override void LoadComplete()
Expand Down Expand Up @@ -209,9 +227,17 @@ private void selectHighestRefreshRate()
{
try
{
var display = gameActivity.WindowManager?.DefaultDisplay;
if (gameActivity.IsFinishing || gameActivity.IsDestroyed)
return;

var window = gameActivity.Window;
var windowManager = gameActivity.WindowManager;

if (window == null || windowManager == null)
return;

if (display == null || gameActivity.Window == null)
var display = windowManager.DefaultDisplay;
if (display == null)
return;

#pragma warning disable CA1422
Expand All @@ -222,17 +248,30 @@ private void selectHighestRefreshRate()
return;

var preferred = modes.OrderByDescending(m => m.RefreshRate).First();
var layoutParams = gameActivity.Window.Attributes;

if (layoutParams != null)
gameActivity.RunOnUiThread(() =>
{
layoutParams.PreferredDisplayModeId = preferred.ModeId;
gameActivity.Window.Attributes = layoutParams;
}
try
{
if (window.Attributes is WindowManagerLayoutParams layoutParams)
{
layoutParams.PreferredDisplayModeId = preferred.ModeId;
window.Attributes = layoutParams;
Debug.WriteLine($"[osu!] Highest refresh rate selected: {preferred.RefreshRate}Hz (mode {preferred.ModeId})");
}
}
catch (Exception e)
{
// On some devices (e.g. Samsung S23 on Android 16), accessing display properties
// via the vendor property 'vendor.display.enable_optimal_refresh_rate' can trigger
// SELinux denials or crashes if the window is not yet fully trusted.
Debug.WriteLine($"[osu!] Failed to apply preferred display mode: {e.Message}");
}
});
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] Failed to select highest refresh rate: {e.Message}");
Debug.WriteLine($"[osu!] Failed to query supported display modes: {e.Message}");
}
}

Expand Down
Loading