1
+ package io .getstream .webrtc .flutter .audio ;
2
+
3
+ import android .content .Context ;
4
+ import android .media .AudioAttributes ;
5
+ import android .media .AudioFocusRequest ;
6
+ import android .media .AudioManager ;
7
+ import android .os .Build ;
8
+ import android .telephony .PhoneStateListener ;
9
+ import android .telephony .TelephonyCallback ;
10
+ import android .telephony .TelephonyManager ;
11
+ import android .util .Log ;
12
+
13
+ import io .getstream .webrtc .flutter .utils .ConstraintsMap ;
14
+
15
+ public class AudioFocusManager {
16
+ private static final String TAG = "AudioFocusManager" ;
17
+
18
+ public enum InterruptionSource {
19
+ AUDIO_FOCUS_ONLY ,
20
+ TELEPHONY_ONLY ,
21
+ AUDIO_FOCUS_AND_TELEPHONY
22
+ }
23
+
24
+ private AudioManager audioManager ;
25
+ private TelephonyManager telephonyManager ;
26
+
27
+ private PhoneStateListener phoneStateListener ;
28
+ private AudioFocusChangeListener focusChangeListener ;
29
+
30
+ private TelephonyCallback telephonyCallback ;
31
+ private AudioFocusRequest audioFocusRequest ;
32
+
33
+ private InterruptionSource interruptionSource ;
34
+ private Context context ;
35
+
36
+ public interface AudioFocusChangeListener {
37
+ void onInterruptionStart ();
38
+ void onInterruptionEnd ();
39
+ }
40
+
41
+ public AudioFocusManager (Context context ) {
42
+ this (context , InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY );
43
+ }
44
+
45
+ public AudioFocusManager (Context context , InterruptionSource interruptionSource ) {
46
+ this .context = context ;
47
+ this .interruptionSource = interruptionSource ;
48
+
49
+ if (interruptionSource == InterruptionSource .AUDIO_FOCUS_ONLY ||
50
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
51
+ audioManager = (AudioManager ) context .getSystemService (Context .AUDIO_SERVICE );
52
+ }
53
+
54
+ if (interruptionSource == InterruptionSource .TELEPHONY_ONLY ||
55
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
56
+ telephonyManager = (TelephonyManager ) context .getSystemService (Context .TELEPHONY_SERVICE );
57
+ }
58
+ }
59
+
60
+ public void setAudioFocusChangeListener (AudioFocusChangeListener listener ) {
61
+ this .focusChangeListener = listener ;
62
+
63
+ if (listener != null ) {
64
+ startMonitoring ();
65
+ } else {
66
+ stopMonitoring ();
67
+ }
68
+ }
69
+
70
+ public void startMonitoring () {
71
+ if (interruptionSource == InterruptionSource .AUDIO_FOCUS_ONLY ||
72
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
73
+ requestAudioFocusInternal ();
74
+ }
75
+
76
+ if (interruptionSource == InterruptionSource .TELEPHONY_ONLY ||
77
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
78
+ registerTelephonyListener ();
79
+ }
80
+ }
81
+
82
+ public void stopMonitoring () {
83
+ if (interruptionSource == InterruptionSource .AUDIO_FOCUS_ONLY ||
84
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
85
+ abandonAudioFocusInternal ();
86
+ }
87
+
88
+ if (interruptionSource == InterruptionSource .TELEPHONY_ONLY ||
89
+ interruptionSource == InterruptionSource .AUDIO_FOCUS_AND_TELEPHONY ) {
90
+ unregisterTelephonyListener ();
91
+ }
92
+ }
93
+
94
+ private void requestAudioFocusInternal () {
95
+ if (audioManager == null ) {
96
+ Log .w (TAG , "AudioManager is null, cannot request audio focus" );
97
+ return ;
98
+ }
99
+
100
+ AudioManager .OnAudioFocusChangeListener onAudioFocusChangeListener = focusChange -> {
101
+ switch (focusChange ) {
102
+ case AudioManager .AUDIOFOCUS_LOSS :
103
+ case AudioManager .AUDIOFOCUS_LOSS_TRANSIENT :
104
+ Log .d (TAG , "Audio focus lost" );
105
+ if (focusChangeListener != null ) {
106
+ focusChangeListener .onInterruptionStart ();
107
+ }
108
+ break ;
109
+ case AudioManager .AUDIOFOCUS_GAIN :
110
+ Log .d (TAG , "Audio focus gained" );
111
+ if (focusChangeListener != null ) {
112
+ focusChangeListener .onInterruptionEnd ();
113
+ }
114
+ break ;
115
+ }
116
+ };
117
+
118
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
119
+ AudioAttributes audioAttributes = new AudioAttributes .Builder ()
120
+ .setUsage (AudioAttributes .USAGE_VOICE_COMMUNICATION )
121
+ .setContentType (AudioAttributes .CONTENT_TYPE_SPEECH )
122
+ .build ();
123
+
124
+ audioFocusRequest = new AudioFocusRequest .Builder (AudioManager .AUDIOFOCUS_GAIN )
125
+ .setAudioAttributes (audioAttributes )
126
+ .setOnAudioFocusChangeListener (onAudioFocusChangeListener )
127
+ .build ();
128
+
129
+ audioManager .requestAudioFocus (audioFocusRequest );
130
+ } else {
131
+ audioManager .requestAudioFocus (onAudioFocusChangeListener ,
132
+ AudioManager .STREAM_VOICE_CALL ,
133
+ AudioManager .AUDIOFOCUS_GAIN );
134
+ }
135
+ }
136
+
137
+ private void registerTelephonyListener () {
138
+ if (telephonyManager == null ) {
139
+ Log .w (TAG , "TelephonyManager is null, cannot register telephony listener" );
140
+ return ;
141
+ }
142
+
143
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
144
+ // Use TelephonyCallback for Android 12+ (API 31+)
145
+ class CallStateCallback extends TelephonyCallback implements TelephonyCallback .CallStateListener {
146
+ @ Override
147
+ public void onCallStateChanged (int state ) {
148
+ handleCallStateChange (state );
149
+ }
150
+ }
151
+ telephonyCallback = new CallStateCallback ();
152
+ telephonyManager .registerTelephonyCallback (context .getMainExecutor (), telephonyCallback );
153
+ } else {
154
+ // Use PhoneStateListener for older Android versions
155
+ phoneStateListener = new PhoneStateListener () {
156
+ @ Override
157
+ public void onCallStateChanged (int state , String phoneNumber ) {
158
+ handleCallStateChange (state );
159
+ }
160
+ };
161
+ telephonyManager .listen (phoneStateListener , PhoneStateListener .LISTEN_CALL_STATE );
162
+ }
163
+ }
164
+
165
+ private void handleCallStateChange (int state ) {
166
+ if (focusChangeListener == null ) {
167
+ return ;
168
+ }
169
+
170
+ switch (state ) {
171
+ case TelephonyManager .CALL_STATE_RINGING :
172
+ case TelephonyManager .CALL_STATE_OFFHOOK :
173
+ Log .d (TAG , "Phone call interruption began" );
174
+ focusChangeListener .onInterruptionStart ();
175
+ break ;
176
+ case TelephonyManager .CALL_STATE_IDLE :
177
+ Log .d (TAG , "Phone call interruption ended" );
178
+ focusChangeListener .onInterruptionEnd ();
179
+ break ;
180
+ }
181
+ }
182
+
183
+ private void abandonAudioFocusInternal () {
184
+ if (audioManager == null ) {
185
+ return ;
186
+ }
187
+
188
+ int result ;
189
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O && audioFocusRequest != null ) {
190
+ result = audioManager .abandonAudioFocusRequest (audioFocusRequest );
191
+ } else {
192
+ result = audioManager .abandonAudioFocus (null );
193
+ }
194
+ }
195
+
196
+ private void unregisterTelephonyListener () {
197
+ if (telephonyManager == null ) {
198
+ return ;
199
+ }
200
+
201
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S && telephonyCallback != null ) {
202
+ telephonyManager .unregisterTelephonyCallback (telephonyCallback );
203
+ telephonyCallback = null ;
204
+ } else if (phoneStateListener != null ) {
205
+ telephonyManager .listen (phoneStateListener , PhoneStateListener .LISTEN_NONE );
206
+ phoneStateListener = null ;
207
+ }
208
+ }
209
+ }
0 commit comments