@@ -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