Skip to content

Commit d514f44

Browse files
authored
Merge pull request #112 from winnerspiros/fix/android-16-startup-crash-1721950423656955281
Fix Android 16 startup crashes related to early system access
2 parents 551fab8 + f5afaaf commit d514f44

2 files changed

Lines changed: 73 additions & 29 deletions

File tree

osu.Android/OsuGameActivity.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class OsuGameActivity : AndroidGameActivity
5151

5252
public new bool IsTablet { get; private set; }
5353

54-
private readonly OsuGameAndroid game;
54+
private OsuGameAndroid? game;
5555

5656
private bool gameCreated;
5757

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

63+
if (game == null)
64+
throw new InvalidOperationException("Game was not initialised.");
65+
6366
gameCreated = true;
6467
return game;
6568
}
@@ -80,6 +83,8 @@ protected override void OnCreate(Bundle? savedInstanceState)
8083
// first use because the internal Platform.CurrentActivity is null.
8184
Microsoft.Maui.ApplicationModel.Platform.Init(this, savedInstanceState);
8285

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

151156
break;
@@ -188,7 +193,7 @@ await Task.WhenAll(uris.Select(async uri =>
188193
}
189194
})).ConfigureAwait(false);
190195

191-
await game.Import(tasks.ToArray()).ConfigureAwait(false);
196+
if (game != null) await game.Import(tasks.ToArray()).ConfigureAwait(false);
192197
}, TaskCreationOptions.LongRunning);
193198
}
194199
}

osu.Android/OsuGameAndroid.cs

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,35 @@ public partial class OsuGameAndroid : OsuGame
2727
[Cached]
2828
private readonly OsuGameActivity gameActivity;
2929

30-
private readonly PackageInfo? packageInfo;
30+
private readonly object packageInfoLock = new object();
31+
private PackageInfo? packageInfo;
32+
private bool packageInfoChecked;
33+
34+
private PackageInfo? getPackageInfo()
35+
{
36+
lock (packageInfoLock)
37+
{
38+
if (packageInfoChecked)
39+
return packageInfo;
40+
41+
try
42+
{
43+
// Use the activity instance directly instead of Application.Context to ensure
44+
// the PackageManager is accessible even on newer/stricter Android versions.
45+
packageInfo = gameActivity.PackageManager?.GetPackageInfo(gameActivity.PackageName!, 0);
46+
}
47+
catch (Exception e)
48+
{
49+
Debug.WriteLine($"[osu!] Failed to retrieve package info: {e.Message}");
50+
}
51+
finally
52+
{
53+
packageInfoChecked = true;
54+
}
55+
56+
return packageInfo;
57+
}
58+
}
3159

3260
public override Vector2 ScalingContainerTargetDrawSize => DrawWidth > 0 && DrawHeight > 0
3361
? new Vector2(1024, 1024 * DrawHeight / DrawWidth)
@@ -53,16 +81,6 @@ public OsuGameAndroid(OsuGameActivity activity)
5381
: base(null)
5482
{
5583
gameActivity = activity;
56-
57-
try
58-
{
59-
packageInfo = Application.Context.ApplicationContext!.PackageManager!.GetPackageInfo(Application.Context.ApplicationContext.PackageName!, 0);
60-
}
61-
catch (Exception e)
62-
{
63-
Debug.WriteLine($"[osu!] Failed to retrieve package info: {e.Message}");
64-
packageInfo = null;
65-
}
6684
}
6785

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

75-
return packageInfo?.VersionName ?? @"unknown";
93+
return getPackageInfo()?.VersionName ?? @"unknown";
7694
}
7795
}
7896

@@ -82,7 +100,7 @@ public override Version AssemblyVersion
82100
{
83101
try
84102
{
85-
string? versionName = packageInfo?.VersionName;
103+
string? versionName = getPackageInfo()?.VersionName;
86104

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

99117
[BackgroundDependencyLoader]
100-
private void load(OsuConfigManager config)
118+
private void load()
101119
{
102-
config.BindWith(OsuSetting.AndroidPerformanceMode, performanceMode);
103-
config.BindWith(OsuSetting.AndroidLowLatencyAudio, lowLatencyAudio);
104-
config.BindWith(OsuSetting.AndroidVulkanProbe, vulkanProbeEnabled);
105-
config.BindWith(OsuSetting.AudioOffset, audioOffset);
120+
LocalConfig.BindWith(OsuSetting.AndroidPerformanceMode, performanceMode);
121+
LocalConfig.BindWith(OsuSetting.AndroidLowLatencyAudio, lowLatencyAudio);
122+
LocalConfig.BindWith(OsuSetting.AndroidVulkanProbe, vulkanProbeEnabled);
123+
LocalConfig.BindWith(OsuSetting.AudioOffset, audioOffset);
106124
}
107125

108126
protected override void LoadComplete()
@@ -209,9 +227,17 @@ private void selectHighestRefreshRate()
209227
{
210228
try
211229
{
212-
var display = gameActivity.WindowManager?.DefaultDisplay;
230+
if (gameActivity.IsFinishing || gameActivity.IsDestroyed)
231+
return;
232+
233+
var window = gameActivity.Window;
234+
var windowManager = gameActivity.WindowManager;
235+
236+
if (window == null || windowManager == null)
237+
return;
213238

214-
if (display == null || gameActivity.Window == null)
239+
var display = windowManager.DefaultDisplay;
240+
if (display == null)
215241
return;
216242

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

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

227-
if (layoutParams != null)
252+
gameActivity.RunOnUiThread(() =>
228253
{
229-
layoutParams.PreferredDisplayModeId = preferred.ModeId;
230-
gameActivity.Window.Attributes = layoutParams;
231-
}
254+
try
255+
{
256+
if (window.Attributes is WindowManagerLayoutParams layoutParams)
257+
{
258+
layoutParams.PreferredDisplayModeId = preferred.ModeId;
259+
window.Attributes = layoutParams;
260+
Debug.WriteLine($"[osu!] Highest refresh rate selected: {preferred.RefreshRate}Hz (mode {preferred.ModeId})");
261+
}
262+
}
263+
catch (Exception e)
264+
{
265+
// On some devices (e.g. Samsung S23 on Android 16), accessing display properties
266+
// via the vendor property 'vendor.display.enable_optimal_refresh_rate' can trigger
267+
// SELinux denials or crashes if the window is not yet fully trusted.
268+
Debug.WriteLine($"[osu!] Failed to apply preferred display mode: {e.Message}");
269+
}
270+
});
232271
}
233272
catch (Exception e)
234273
{
235-
Debug.WriteLine($"[osu!] Failed to select highest refresh rate: {e.Message}");
274+
Debug.WriteLine($"[osu!] Failed to query supported display modes: {e.Message}");
236275
}
237276
}
238277

0 commit comments

Comments
 (0)