11// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22// See the LICENCE file in the repository root for full licence text.
3+ using System . Diagnostics ;
4+ using Microsoft . Maui . Devices ;
5+ using osu . Android . Performance ;
6+ using osu . Framework . Development ;
37
4- #pragma warning disable CA1422
5- #pragma warning restore CA1422
6-
7- using Android . App ;
88using Android . Content . PM ;
9- using Android . Content ;
10- using Android . Media ;
11- using Android . Views ;
9+ using osu . Game . Performance ;
10+ using osu . Game . Updater ;
11+ using System . Collections . Specialized ;
12+ using System ;
13+ using System . Collections ;
14+ using System . Collections . Generic ;
1215using Debug = System . Diagnostics . Debug ;
13- using Microsoft . Maui . Devices ;
1416using System . Linq ;
17+ using System . Reflection ;
1518using System . Runtime . CompilerServices ;
16- using System ;
17- using System . Diagnostics ;
19+ using Context = global ::Android . Content . Context ;
20+ using Android . Media ;
21+ using Android . OS ;
22+ using Android . Views ;
1823using osu . Android . Native ;
1924using osu . Framework . Allocation ;
25+ using AudioManager = osu . Framework . Audio . AudioManager ;
2026using osu . Framework . Bindables ;
21- using osu . Framework . Development ;
27+ using osu . Framework . Configuration ;
28+ using osu . Framework . Extensions . IEnumerableExtensions ;
29+ using osu . Framework . Graphics ;
30+ using osu . Framework . Input ;
2231using osu . Framework . Platform ;
32+ using osu . Framework . Threading ;
33+ using osu . Game ;
2334using osu . Game . Configuration ;
35+ using osu . Game . Overlays ;
36+ using osu . Game . Overlays . Notifications ;
2437using osu . Game . Screens ;
25- using osu . Game . Updater ;
2638using osu . Game . Utils ;
27- using osu . Game ;
28- using osuTK ;
29- using osu . Game . Performance ;
30- using osu . Android . Performance ;
39+ using Vector2 = osuTK . Vector2 ;
3140
3241namespace osu . Android
3342{
3443 public partial class OsuGameAndroid : OsuGame
3544 {
36- [ Cached ]
3745 private readonly OsuGameActivity gameActivity ;
3846
3947 private readonly object packageInfoLock = new object ( ) ;
40-
4148 private PackageInfo ? packageInfo ;
4249 private bool packageInfoChecked ;
4350
@@ -77,6 +84,8 @@ public partial class OsuGameAndroid : OsuGame
7784 private readonly IHighPerformanceSessionManager highPerformanceSessionManager = new AndroidHighPerformanceSessionManager ( ) ;
7885
7986 private OboeAudioRedirector ? audioRedirector ;
87+ private Delegate ? activeMixersHandler ;
88+ private object ? activeMixersList ;
8089 private IntPtr updateAdpfSession ;
8190 private IntPtr renderAdpfSession ;
8291
@@ -102,7 +111,7 @@ public override string Version
102111 get
103112 {
104113 if ( ! IsDeployedBuild )
105- return @"local " + ( DebugUtils . IsDebugBuild ? @"debug" : @"release" ) ;
114+ return @"local " + ( osu . Framework . Development . DebugUtils . IsDebugBuild ? @"debug" : @"release" ) ;
106115
107116 return getPackageInfo ( ) ? . VersionName ?? @"unknown" ;
108117 }
@@ -137,6 +146,30 @@ private void load()
137146 LocalConfig . BindWith ( OsuSetting . AudioOffset , audioOffset ) ;
138147
139148 audioRedirector = new OboeAudioRedirector ( Audio ) ;
149+
150+ try
151+ {
152+ // Use reflection to bind to collection changes of the internal activeMixers list in AudioManager.
153+ FieldInfo ? field = typeof ( AudioManager ) . GetField ( "activeMixers" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
154+ if ( field != null )
155+ {
156+ activeMixersList = field . GetValue ( Audio ) ;
157+ object ? val = field . GetValue ( Audio ) ;
158+ if ( val != null )
159+ {
160+ MethodInfo ? bindMethod = val . GetType ( ) . GetMethod ( "BindCollectionChanged" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
161+ if ( bindMethod != null )
162+ {
163+ var del = Delegate . CreateDelegate ( bindMethod . GetParameters ( ) [ 0 ] . ParameterType , this , typeof ( OsuGameAndroid ) . GetMethod ( nameof ( onActiveMixersChanged ) , BindingFlags . Instance | BindingFlags . NonPublic ) ! ) ;
164+ bindMethod . Invoke ( val , new object [ ] { del , true } ) ;
165+ }
166+ }
167+ }
168+ }
169+ catch ( Exception ex )
170+ {
171+ Debug . WriteLine ( $ "[osu!] Failed to bind to activeMixers via reflection: { ex . Message } ") ;
172+ }
140173 }
141174
142175 [ MethodImpl ( MethodImplOptions . AggressiveOptimization ) ]
@@ -215,9 +248,9 @@ protected override void LoadComplete()
215248 int hardwareSampleRate = 0 ;
216249 try
217250 {
218- if ( gameActivity . GetSystemService ( Context . AudioService ) is AudioManager audioManager )
251+ if ( gameActivity . GetSystemService ( global :: Android . Content . Context . AudioService ) is global :: Android . Media . AudioManager audioManager )
219252 {
220- string ? rateStr = audioManager . GetProperty ( AudioManager . PropertyOutputSampleRate ) ;
253+ string ? rateStr = audioManager . GetProperty ( global :: Android . Media . AudioManager . PropertyOutputSampleRate ) ;
221254
222255 if ( ! string . IsNullOrEmpty ( rateStr ) )
223256 hardwareSampleRate = int . Parse ( rateStr ) ;
@@ -360,7 +393,11 @@ private void selectHighestRefreshRate()
360393 }
361394 }
362395
363- public bool IsVulkanRecommended ( ) => ( nativeBridges as AndroidNativeBridgeManager ) ? . IsVulkanRecommended ( ) ?? false ;
396+ public override bool IsVulkanRecommended => ( nativeBridges as AndroidNativeBridgeManager ) ? . IsVulkanRecommended ( ) ?? false ;
397+
398+ public override bool IsVulkanSupported => ( nativeBridges as AndroidNativeBridgeManager ) ? . IsVulkanAvailable ( ) ?? false ;
399+
400+ private void onActiveMixersChanged ( object ? sender , NotifyCollectionChangedEventArgs args ) => Schedule ( ( ) => { if ( lowLatencyAudio . Value ) audioRedirector ? . RefreshMixers ( 0 ) ; } ) ;
364401
365402 public double GetMeasuredAudioLatencyMs ( ) => getMeasuredAudioLatencyFromBridge ( ) ;
366403
@@ -371,9 +408,9 @@ private void startOboeBridge(Action<double> onLatencyMeasured, IntPtr provider,
371408
372409 try
373410 {
374- if ( gameActivity . GetSystemService ( Context . AudioService ) is AudioManager audioManager )
411+ if ( gameActivity . GetSystemService ( global :: Android . Content . Context . AudioService ) is global :: Android . Media . AudioManager audioManager )
375412 {
376- string ? rateStr = audioManager . GetProperty ( AudioManager . PropertyOutputSampleRate ) ;
413+ string ? rateStr = audioManager . GetProperty ( global :: Android . Media . AudioManager . PropertyOutputSampleRate ) ;
377414
378415 if ( ! string . IsNullOrEmpty ( rateStr ) )
379416 hardwareSampleRate = int . Parse ( rateStr ) ;
@@ -443,11 +480,11 @@ private void updateOrientation()
443480 switch ( orientation )
444481 {
445482 case MobileUtils . Orientation . Locked :
446- gameActivity . RequestedOrientation = ScreenOrientation . Locked ;
483+ gameActivity . RequestedOrientation = global :: Android . Content . PM . ScreenOrientation . Locked ;
447484 break ;
448485
449486 case MobileUtils . Orientation . Portrait :
450- gameActivity . RequestedOrientation = ScreenOrientation . Portrait ;
487+ gameActivity . RequestedOrientation = global :: Android . Content . PM . ScreenOrientation . Portrait ;
451488 break ;
452489
453490 case MobileUtils . Orientation . Default :
@@ -485,6 +522,18 @@ protected override void Dispose(bool isDisposing)
485522 audioRedirector ? . Dispose ( ) ;
486523 audioRedirector = null ;
487524
525+ if ( activeMixersList != null && activeMixersHandler != null )
526+ {
527+ try
528+ {
529+ MethodInfo ? unbindMethod = activeMixersList . GetType ( ) . GetMethod ( "UnbindCollectionChanged" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
530+ unbindMethod ? . Invoke ( activeMixersList , new object [ ] { activeMixersHandler } ) ;
531+ }
532+ catch { }
533+ activeMixersList = null ;
534+ activeMixersHandler = null ;
535+ }
536+
488537 if ( nativeBridges != null )
489538 disposeNativeBridges ( ) ;
490539
@@ -524,6 +573,6 @@ protected override void UpdateAfterChildren()
524573 internal class AndroidBatteryInfo : BatteryInfo
525574 {
526575 public override double ? ChargeLevel => Microsoft . Maui . Devices . Battery . ChargeLevel ;
527- public override bool OnBattery => Microsoft . Maui . Devices . Battery . PowerSource == BatteryPowerSource . Battery ;
576+ public override bool OnBattery => Microsoft . Maui . Devices . Battery . PowerSource == global :: Microsoft . Maui . Devices . BatteryPowerSource . Battery ;
528577 }
529- }
578+ }
0 commit comments