OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. | 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 package org.appspot.apprtc; | 11 package org.appspot.apprtc; |
12 | 12 |
13 import org.appspot.apprtc.util.AppRTCUtils; | 13 import org.appspot.apprtc.util.AppRTCUtils; |
14 | 14 |
15 import android.content.BroadcastReceiver; | 15 import android.content.BroadcastReceiver; |
16 import android.content.Context; | 16 import android.content.Context; |
17 import android.content.Intent; | 17 import android.content.Intent; |
18 import android.content.IntentFilter; | 18 import android.content.IntentFilter; |
19 import android.content.SharedPreferences; | 19 import android.content.SharedPreferences; |
20 import android.content.pm.PackageManager; | 20 import android.content.pm.PackageManager; |
21 import android.media.AudioManager; | 21 import android.media.AudioManager; |
22 import android.preference.PreferenceManager; | 22 import android.preference.PreferenceManager; |
23 import android.util.Log; | 23 import android.util.Log; |
24 | 24 |
| 25 import org.webrtc.ThreadUtils; |
| 26 |
25 import java.util.Collections; | 27 import java.util.Collections; |
26 import java.util.HashSet; | 28 import java.util.HashSet; |
| 29 import java.util.List; |
27 import java.util.Set; | 30 import java.util.Set; |
28 | 31 |
29 /** | 32 /** |
30 * AppRTCAudioManager manages all audio related parts of the AppRTC demo. | 33 * AppRTCAudioManager manages all audio related parts of the AppRTC demo. |
31 */ | 34 */ |
32 public class AppRTCAudioManager { | 35 public class AppRTCAudioManager { |
33 private static final String TAG = "AppRTCAudioManager"; | 36 private static final String TAG = "AppRTCAudioManager"; |
34 private static final String SPEAKERPHONE_AUTO = "auto"; | 37 private static final String SPEAKERPHONE_AUTO = "auto"; |
35 private static final String SPEAKERPHONE_TRUE = "true"; | 38 private static final String SPEAKERPHONE_TRUE = "true"; |
36 private static final String SPEAKERPHONE_FALSE = "false"; | 39 private static final String SPEAKERPHONE_FALSE = "false"; |
37 | 40 |
38 /** | 41 /** |
39 * AudioDevice is the names of possible audio devices that we currently | 42 * AudioDevice is the names of possible audio devices that we currently |
40 * support. | 43 * support. |
41 */ | 44 */ |
42 // TODO(henrika): add support for BLUETOOTH as well. | 45 public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, N
ONE } |
43 public enum AudioDevice { | 46 |
44 SPEAKER_PHONE, | 47 /** AudioManager state. */ |
45 WIRED_HEADSET, | 48 public enum AudioManagerState { |
46 EARPIECE, | 49 UNINITIALIZED, |
| 50 PREINITIALIZED, |
| 51 RUNNING, |
| 52 } |
| 53 |
| 54 /** Selected audio device change event. */ |
| 55 public static interface AudioManagerEvents { |
| 56 // Callback fired once audio device is changed or list of available audio de
vices changed. |
| 57 void onAudioDeviceChanged( |
| 58 AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices)
; |
47 } | 59 } |
48 | 60 |
49 private final Context apprtcContext; | 61 private final Context apprtcContext; |
50 private final Runnable onStateChangeListener; | |
51 private boolean initialized = false; | |
52 private AudioManager audioManager; | 62 private AudioManager audioManager; |
| 63 |
| 64 private AudioManagerEvents audioManagerEvents; |
| 65 private AudioManagerState amState; |
53 private int savedAudioMode = AudioManager.MODE_INVALID; | 66 private int savedAudioMode = AudioManager.MODE_INVALID; |
54 private boolean savedIsSpeakerPhoneOn = false; | 67 private boolean savedIsSpeakerPhoneOn = false; |
55 private boolean savedIsMicrophoneMute = false; | 68 private boolean savedIsMicrophoneMute = false; |
| 69 private boolean hasWiredHeadset = false; |
56 | 70 |
57 private final AudioDevice defaultAudioDevice; | 71 // Default audio device; speaker phone for video calls or earpiece for audio |
| 72 // only calls. |
| 73 private AudioDevice defaultAudioDevice; |
| 74 |
| 75 // Contains the currently selected audio device. |
| 76 // This device is changed automatically using a certain scheme where e.g. |
| 77 // a wired headset "wins" over speaker phone. It is also possible for a |
| 78 // user to explicitly select a device (and overrid any predefined scheme). |
| 79 // See |userSelectedAudioDevice| for details. |
| 80 private AudioDevice selectedAudioDevice; |
| 81 |
| 82 // Contains the user-selected audio device which overrides the predefined |
| 83 // selection scheme. |
| 84 // TODO(henrika): always set to AudioDevice.NONE today. Add support for |
| 85 // explicit selection based on choice by userSelectedAudioDevice. |
| 86 private AudioDevice userSelectedAudioDevice; |
58 | 87 |
59 // Contains speakerphone setting: auto, true or false | 88 // Contains speakerphone setting: auto, true or false |
60 private final String useSpeakerphone; | 89 private final String useSpeakerphone; |
61 | 90 |
62 // Proximity sensor object. It measures the proximity of an object in cm | 91 // Proximity sensor object. It measures the proximity of an object in cm |
63 // relative to the view screen of a device and can therefore be used to | 92 // relative to the view screen of a device and can therefore be used to |
64 // assist device switching (close to ear <=> use headset earpiece if | 93 // assist device switching (close to ear <=> use headset earpiece if |
65 // available, far from ear <=> use speaker phone). | 94 // available, far from ear <=> use speaker phone). |
66 private AppRTCProximitySensor proximitySensor = null; | 95 private AppRTCProximitySensor proximitySensor = null; |
67 | 96 |
68 // Contains the currently selected audio device. | 97 // Handles all tasks related to Bluetooth headset devices. |
69 private AudioDevice selectedAudioDevice; | 98 private final AppRTCBluetoothManager bluetoothManager; |
70 | 99 |
71 // Contains a list of available audio devices. A Set collection is used to | 100 // Contains a list of available audio devices. A Set collection is used to |
72 // avoid duplicate elements. | 101 // avoid duplicate elements. |
73 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>(); | 102 private Set<AudioDevice> audioDevices = new HashSet<AudioDevice>(); |
74 | 103 |
75 // Broadcast receiver for wired headset intent broadcasts. | 104 // Broadcast receiver for wired headset intent broadcasts. |
76 private BroadcastReceiver wiredHeadsetReceiver; | 105 private BroadcastReceiver wiredHeadsetReceiver; |
77 | 106 |
78 // Callback method for changes in audio focus. | 107 // Callback method for changes in audio focus. |
79 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; | 108 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; |
80 | 109 |
81 // This method is called when the proximity sensor reports a state change, | 110 /** |
82 // e.g. from "NEAR to FAR" or from "FAR to NEAR". | 111 * This method is called when the proximity sensor reports a state change, |
| 112 * e.g. from "NEAR to FAR" or from "FAR to NEAR". |
| 113 */ |
83 private void onProximitySensorChangedState() { | 114 private void onProximitySensorChangedState() { |
84 if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { | 115 if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { |
85 return; | 116 return; |
86 } | 117 } |
87 | 118 |
88 // The proximity sensor should only be activated when there are exactly two | 119 // The proximity sensor should only be activated when there are exactly two |
89 // available audio devices. | 120 // available audio devices. |
90 if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.Aud
ioDevice.EARPIECE) | 121 if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.Aud
ioDevice.EARPIECE) |
91 && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE))
{ | 122 && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE))
{ |
92 if (proximitySensor.sensorReportsNearState()) { | 123 if (proximitySensor.sensorReportsNearState()) { |
93 // Sensor reports that a "handset is being held up to a person's ear", | 124 // Sensor reports that a "handset is being held up to a person's ear", |
94 // or "something is covering the light sensor". | 125 // or "something is covering the light sensor". |
95 setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); | 126 setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.EARPIECE); |
96 } else { | 127 } else { |
97 // Sensor reports that a "handset is removed from a person's ear", or | 128 // Sensor reports that a "handset is removed from a person's ear", or |
98 // "the light sensor is no longer covered". | 129 // "the light sensor is no longer covered". |
99 setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); | 130 setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); |
100 } | 131 } |
101 } | 132 } |
102 } | 133 } |
103 | 134 |
104 /** Construction */ | 135 /* Receiver which handles changes in wired headset availability. */ |
105 static AppRTCAudioManager create(Context context, Runnable deviceStateChangeLi
stener) { | 136 private class WiredHeadsetReceiver extends BroadcastReceiver { |
106 return new AppRTCAudioManager(context, deviceStateChangeListener); | 137 private static final int STATE_UNPLUGGED = 0; |
| 138 private static final int STATE_PLUGGED = 1; |
| 139 private static final int HAS_NO_MIC = 0; |
| 140 private static final int HAS_MIC = 1; |
| 141 |
| 142 @Override |
| 143 public void onReceive(Context context, Intent intent) { |
| 144 int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
| 145 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
| 146 String name = intent.getStringExtra("name"); |
| 147 Log.d(TAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo()
+ ": " |
| 148 + "a=" + intent.getAction() + ", s=" |
| 149 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" |
| 150 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ",
sb=" |
| 151 + isInitialStickyBroadcast()); |
| 152 hasWiredHeadset = (state == STATE_PLUGGED); |
| 153 updateAudioDeviceState(); |
| 154 } |
| 155 }; |
| 156 |
| 157 /** Construction. */ |
| 158 static AppRTCAudioManager create(Context context) { |
| 159 return new AppRTCAudioManager(context); |
107 } | 160 } |
108 | 161 |
109 private AppRTCAudioManager(Context context, Runnable deviceStateChangeListener
) { | 162 private AppRTCAudioManager(Context context) { |
| 163 Log.d(TAG, "ctor"); |
| 164 ThreadUtils.checkIsOnMainThread(); |
110 apprtcContext = context; | 165 apprtcContext = context; |
111 onStateChangeListener = deviceStateChangeListener; | |
112 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC
E)); | 166 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC
E)); |
| 167 bluetoothManager = AppRTCBluetoothManager.create(context, this); |
| 168 wiredHeadsetReceiver = new WiredHeadsetReceiver(); |
| 169 amState = AudioManagerState.UNINITIALIZED; |
113 | 170 |
114 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref
erences(context); | 171 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref
erences(context); |
115 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre
f_speakerphone_key), | 172 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre
f_speakerphone_key), |
116 context.getString(R.string.pref_speakerphone_default)); | 173 context.getString(R.string.pref_speakerphone_default)); |
117 | 174 Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); |
118 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { | 175 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { |
119 defaultAudioDevice = AudioDevice.EARPIECE; | 176 defaultAudioDevice = AudioDevice.EARPIECE; |
120 } else { | 177 } else { |
121 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; | 178 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
122 } | 179 } |
123 | 180 |
124 // Create and initialize the proximity sensor. | 181 // Create and initialize the proximity sensor. |
125 // Tablet devices (e.g. Nexus 7) does not support proximity sensors. | 182 // Tablet devices (e.g. Nexus 7) does not support proximity sensors. |
126 // Note that, the sensor will not be active until start() has been called. | 183 // Note that, the sensor will not be active until start() has been called. |
127 proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { | 184 proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { |
128 // This method will be called each time a state change is detected. | 185 // This method will be called each time a state change is detected. |
129 // Example: user holds his hand over the device (closer than ~5 cm), | 186 // Example: user holds his hand over the device (closer than ~5 cm), |
130 // or removes his hand from the device. | 187 // or removes his hand from the device. |
131 public void run() { | 188 public void run() { |
132 onProximitySensorChangedState(); | 189 onProximitySensorChangedState(); |
133 } | 190 } |
134 }); | 191 }); |
| 192 |
| 193 Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); |
135 AppRTCUtils.logDeviceInfo(TAG); | 194 AppRTCUtils.logDeviceInfo(TAG); |
136 } | 195 } |
137 | 196 |
138 public void init() { | 197 public void start(AudioManagerEvents audioManagerEvents) { |
139 Log.d(TAG, "init"); | 198 Log.d(TAG, "start"); |
140 if (initialized) { | 199 ThreadUtils.checkIsOnMainThread(); |
| 200 if (amState == AudioManagerState.RUNNING) { |
| 201 Log.e(TAG, "AudioManager is already active"); |
141 return; | 202 return; |
142 } | 203 } |
| 204 // TODO(henrika): perhaps call new method called preInitAudio() here if UNIN
ITIALIZED. |
143 | 205 |
144 // Store current audio state so we can restore it when close() is called. | 206 Log.d(TAG, "AudioManager starts..."); |
| 207 this.audioManagerEvents = audioManagerEvents; |
| 208 amState = AudioManagerState.RUNNING; |
| 209 |
| 210 // Store current audio state so we can restore it when stop() is called. |
145 savedAudioMode = audioManager.getMode(); | 211 savedAudioMode = audioManager.getMode(); |
146 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); | 212 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); |
147 savedIsMicrophoneMute = audioManager.isMicrophoneMute(); | 213 savedIsMicrophoneMute = audioManager.isMicrophoneMute(); |
| 214 hasWiredHeadset = hasWiredHeadset(); |
148 | 215 |
149 // Create an AudioManager.OnAudioFocusChangeListener instance. | 216 // Create an AudioManager.OnAudioFocusChangeListener instance. |
150 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { | 217 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { |
151 // Called on the listener to notify if the audio focus for this listener h
as been changed. | 218 // Called on the listener to notify if the audio focus for this listener h
as been changed. |
152 // The |focusChange| value indicates whether the focus was gained, whether
the focus was lost, | 219 // The |focusChange| value indicates whether the focus was gained, whether
the focus was lost, |
153 // and whether that loss is transient, or whether the new focus holder wil
l hold it for an | 220 // and whether that loss is transient, or whether the new focus holder wil
l hold it for an |
154 // unknown amount of time. | 221 // unknown amount of time. |
155 // TODO(henrika): possibly extend support of handling audio-focus changes.
Only contains | 222 // TODO(henrika): possibly extend support of handling audio-focus changes.
Only contains |
156 // logging for now. | 223 // logging for now. |
157 @Override | 224 @Override |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
192 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); | 259 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); |
193 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { | 260 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
194 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams"); | 261 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams"); |
195 } else { | 262 } else { |
196 Log.e(TAG, "Audio focus request failed"); | 263 Log.e(TAG, "Audio focus request failed"); |
197 } | 264 } |
198 | 265 |
199 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is | 266 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is |
200 // required to be in this mode when playout and/or recording starts for | 267 // required to be in this mode when playout and/or recording starts for |
201 // best possible VoIP performance. | 268 // best possible VoIP performance. |
202 // TODO(henrika): we migh want to start with RINGTONE mode here instead. | |
203 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); | 269 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); |
204 | 270 |
205 // Always disable microphone mute during a WebRTC call. | 271 // Always disable microphone mute during a WebRTC call. |
206 setMicrophoneMute(false); | 272 setMicrophoneMute(false); |
207 | 273 |
| 274 // Set initial device states. |
| 275 userSelectedAudioDevice = AudioDevice.NONE; |
| 276 selectedAudioDevice = AudioDevice.NONE; |
| 277 audioDevices.clear(); |
| 278 |
| 279 // Initialize and start Bluetooth if a BT device is available or initiate |
| 280 // detection of new (enabled) BT devices. |
| 281 bluetoothManager.start(); |
| 282 |
208 // Do initial selection of audio device. This setting can later be changed | 283 // Do initial selection of audio device. This setting can later be changed |
209 // either by adding/removing a wired headset or by covering/uncovering the | 284 // either by adding/removing a BT or wired headset or by covering/uncovering |
210 // proximity sensor. | 285 // the proximity sensor. |
211 updateAudioDeviceState(hasWiredHeadset()); | 286 updateAudioDeviceState(); |
212 | 287 |
213 // Register receiver for broadcast intents related to adding/removing a | 288 // Register receiver for broadcast intents related to adding/removing a |
214 // wired headset (Intent.ACTION_HEADSET_PLUG). | 289 // wired headset. |
215 registerForWiredHeadsetIntentBroadcast(); | 290 registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSE
T_PLUG)); |
216 | 291 Log.d(TAG, "AudioManager started"); |
217 initialized = true; | |
218 } | 292 } |
219 | 293 |
220 public void close() { | 294 public void stop() { |
221 Log.d(TAG, "close"); | 295 Log.d(TAG, "stop"); |
222 if (!initialized) { | 296 ThreadUtils.checkIsOnMainThread(); |
| 297 if (amState != AudioManagerState.RUNNING) { |
| 298 Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState); |
223 return; | 299 return; |
224 } | 300 } |
| 301 amState = AudioManagerState.UNINITIALIZED; |
225 | 302 |
226 unregisterForWiredHeadsetIntentBroadcast(); | 303 unregisterReceiver(wiredHeadsetReceiver); |
| 304 |
| 305 bluetoothManager.stop(); |
227 | 306 |
228 // Restore previously stored audio states. | 307 // Restore previously stored audio states. |
229 setSpeakerphoneOn(savedIsSpeakerPhoneOn); | 308 setSpeakerphoneOn(savedIsSpeakerPhoneOn); |
230 setMicrophoneMute(savedIsMicrophoneMute); | 309 setMicrophoneMute(savedIsMicrophoneMute); |
231 audioManager.setMode(savedAudioMode); | 310 audioManager.setMode(savedAudioMode); |
232 | 311 |
233 // Abandon audio focus. Gives the previous focus owner, if any, focus. | 312 // Abandon audio focus. Gives the previous focus owner, if any, focus. |
234 audioManager.abandonAudioFocus(audioFocusChangeListener); | 313 audioManager.abandonAudioFocus(audioFocusChangeListener); |
235 audioFocusChangeListener = null; | 314 audioFocusChangeListener = null; |
236 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); | 315 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); |
237 | 316 |
238 if (proximitySensor != null) { | 317 if (proximitySensor != null) { |
239 proximitySensor.stop(); | 318 proximitySensor.stop(); |
240 proximitySensor = null; | 319 proximitySensor = null; |
241 } | 320 } |
242 | 321 |
243 initialized = false; | 322 audioManagerEvents = null; |
| 323 Log.d(TAG, "AudioManager stopped"); |
244 } | 324 } |
245 | 325 |
246 /** Changes selection of the currently active audio device. */ | 326 /** Changes selection of the currently active audio device. */ |
247 public void setAudioDevice(AudioDevice device) { | 327 private void setAudioDeviceInternal(AudioDevice device) { |
248 Log.d(TAG, "setAudioDevice(device=" + device + ")"); | 328 Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")"); |
249 AppRTCUtils.assertIsTrue(audioDevices.contains(device)); | 329 AppRTCUtils.assertIsTrue(audioDevices.contains(device)); |
250 | 330 |
251 switch (device) { | 331 switch (device) { |
252 case SPEAKER_PHONE: | 332 case SPEAKER_PHONE: |
253 setSpeakerphoneOn(true); | 333 setSpeakerphoneOn(true); |
254 selectedAudioDevice = AudioDevice.SPEAKER_PHONE; | |
255 break; | 334 break; |
256 case EARPIECE: | 335 case EARPIECE: |
257 setSpeakerphoneOn(false); | 336 setSpeakerphoneOn(false); |
258 selectedAudioDevice = AudioDevice.EARPIECE; | |
259 break; | 337 break; |
260 case WIRED_HEADSET: | 338 case WIRED_HEADSET: |
261 setSpeakerphoneOn(false); | 339 setSpeakerphoneOn(false); |
262 selectedAudioDevice = AudioDevice.WIRED_HEADSET; | 340 break; |
| 341 case BLUETOOTH: |
| 342 setSpeakerphoneOn(false); |
263 break; | 343 break; |
264 default: | 344 default: |
265 Log.e(TAG, "Invalid audio device selection"); | 345 Log.e(TAG, "Invalid audio device selection"); |
266 break; | 346 break; |
267 } | 347 } |
268 onAudioManagerChangedState(); | 348 selectedAudioDevice = device; |
| 349 } |
| 350 |
| 351 /** |
| 352 * Changes default audio device. |
| 353 * TODO(henrika): add usage of this method in the AppRTCMobile client. |
| 354 */ |
| 355 public void setDefaultAudioDevice(AudioDevice defaultDevice) { |
| 356 ThreadUtils.checkIsOnMainThread(); |
| 357 switch (defaultDevice) { |
| 358 case SPEAKER_PHONE: |
| 359 defaultAudioDevice = defaultDevice; |
| 360 break; |
| 361 case EARPIECE: |
| 362 if (hasEarpiece()) { |
| 363 defaultAudioDevice = defaultDevice; |
| 364 } else { |
| 365 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
| 366 } |
| 367 break; |
| 368 default: |
| 369 Log.e(TAG, "Invalid default audio device selection"); |
| 370 break; |
| 371 } |
| 372 Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); |
| 373 updateAudioDeviceState(); |
| 374 } |
| 375 |
| 376 /** Changes selection of the currently active audio device. */ |
| 377 public void selectAudioDevice(AudioDevice device) { |
| 378 ThreadUtils.checkIsOnMainThread(); |
| 379 if (!audioDevices.contains(device)) { |
| 380 Log.e(TAG, "Can not select " + device + " from available " + audioDevices)
; |
| 381 } |
| 382 userSelectedAudioDevice = device; |
| 383 updateAudioDeviceState(); |
269 } | 384 } |
270 | 385 |
271 /** Returns current set of available/selectable audio devices. */ | 386 /** Returns current set of available/selectable audio devices. */ |
272 public Set<AudioDevice> getAudioDevices() { | 387 public Set<AudioDevice> getAudioDevices() { |
| 388 ThreadUtils.checkIsOnMainThread(); |
273 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); | 389 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); |
274 } | 390 } |
275 | 391 |
276 /** Returns the currently selected audio device. */ | 392 /** Returns the currently selected audio device. */ |
277 public AudioDevice getSelectedAudioDevice() { | 393 public AudioDevice getSelectedAudioDevice() { |
| 394 ThreadUtils.checkIsOnMainThread(); |
278 return selectedAudioDevice; | 395 return selectedAudioDevice; |
279 } | 396 } |
280 | 397 |
281 /** | 398 /** Helper method for receiver registration. */ |
282 * Registers receiver for the broadcasted intent when a wired headset is | 399 private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
{ |
283 * plugged in or unplugged. The received intent will have an extra | 400 apprtcContext.registerReceiver(receiver, filter); |
284 * 'state' value where 0 means unplugged, and 1 means plugged. | |
285 */ | |
286 private void registerForWiredHeadsetIntentBroadcast() { | |
287 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); | |
288 | |
289 /** Receiver which handles changes in wired headset availability. */ | |
290 wiredHeadsetReceiver = new BroadcastReceiver() { | |
291 private static final int STATE_UNPLUGGED = 0; | |
292 private static final int STATE_PLUGGED = 1; | |
293 private static final int HAS_NO_MIC = 0; | |
294 private static final int HAS_MIC = 1; | |
295 | |
296 @Override | |
297 public void onReceive(Context context, Intent intent) { | |
298 int state = intent.getIntExtra("state", STATE_UNPLUGGED); | |
299 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); | |
300 String name = intent.getStringExtra("name"); | |
301 Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo() +
": " | |
302 + "a=" + intent.getAction() + ", s=" | |
303 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" | |
304 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + "
, sb=" | |
305 + isInitialStickyBroadcast()); | |
306 | |
307 boolean hasWiredHeadset = (state == STATE_PLUGGED); | |
308 switch (state) { | |
309 case STATE_UNPLUGGED: | |
310 updateAudioDeviceState(hasWiredHeadset); | |
311 break; | |
312 case STATE_PLUGGED: | |
313 if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) { | |
314 updateAudioDeviceState(hasWiredHeadset); | |
315 } | |
316 break; | |
317 default: | |
318 Log.e(TAG, "Invalid state"); | |
319 break; | |
320 } | |
321 } | |
322 }; | |
323 | |
324 apprtcContext.registerReceiver(wiredHeadsetReceiver, filter); | |
325 } | 401 } |
326 | 402 |
327 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ | 403 /** Helper method for unregistration of an existing receiver. */ |
328 private void unregisterForWiredHeadsetIntentBroadcast() { | 404 private void unregisterReceiver(BroadcastReceiver receiver) { |
329 apprtcContext.unregisterReceiver(wiredHeadsetReceiver); | 405 apprtcContext.unregisterReceiver(receiver); |
330 wiredHeadsetReceiver = null; | |
331 } | 406 } |
332 | 407 |
333 /** Sets the speaker phone mode. */ | 408 /** Sets the speaker phone mode. */ |
334 private void setSpeakerphoneOn(boolean on) { | 409 private void setSpeakerphoneOn(boolean on) { |
335 boolean wasOn = audioManager.isSpeakerphoneOn(); | 410 boolean wasOn = audioManager.isSpeakerphoneOn(); |
336 if (wasOn == on) { | 411 if (wasOn == on) { |
337 return; | 412 return; |
338 } | 413 } |
339 audioManager.setSpeakerphoneOn(on); | 414 audioManager.setSpeakerphoneOn(on); |
340 } | 415 } |
(...skipping 17 matching lines...) Expand all Loading... |
358 * This is not a valid indication that audio playback is actually over | 433 * This is not a valid indication that audio playback is actually over |
359 * the wired headset as audio routing depends on other conditions. We | 434 * the wired headset as audio routing depends on other conditions. We |
360 * only use it as an early indicator (during initialization) of an attached | 435 * only use it as an early indicator (during initialization) of an attached |
361 * wired headset. | 436 * wired headset. |
362 */ | 437 */ |
363 @Deprecated | 438 @Deprecated |
364 private boolean hasWiredHeadset() { | 439 private boolean hasWiredHeadset() { |
365 return audioManager.isWiredHeadsetOn(); | 440 return audioManager.isWiredHeadsetOn(); |
366 } | 441 } |
367 | 442 |
368 /** Update list of possible audio devices and make new device selection. */ | 443 /** |
369 private void updateAudioDeviceState(boolean hasWiredHeadset) { | 444 * Updates list of possible audio devices and make new device selection. |
370 // Update the list of available audio devices. | 445 * TODO(henrika): add unit test to verify all state transitions. |
371 audioDevices.clear(); | 446 */ |
| 447 public void updateAudioDeviceState() { |
| 448 ThreadUtils.checkIsOnMainThread(); |
| 449 Log.d(TAG, "--- updateAudioDeviceState: " |
| 450 + "wired headset=" + hasWiredHeadset + ", " |
| 451 + "BT state=" + bluetoothManager.getState()); |
| 452 Log.d(TAG, "Device status: " |
| 453 + "available=" + audioDevices + ", " |
| 454 + "selected=" + selectedAudioDevice + ", " |
| 455 + "user selected=" + userSelectedAudioDevice); |
| 456 |
| 457 // Check if any Bluetooth headset is connected. The internal BT state will |
| 458 // change accordingly. |
| 459 // TODO(henrika): perhaps wrap required state into BT manager. |
| 460 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI
LABLE |
| 461 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_U
NAVAILABLE |
| 462 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCO
NNECTING) { |
| 463 bluetoothManager.updateDevice(); |
| 464 } |
| 465 |
| 466 // Update the set of available audio devices. |
| 467 Set<AudioDevice> newAudioDevices = new HashSet<>(); |
| 468 |
| 469 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTE
D |
| 470 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE
CTING |
| 471 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_A
VAILABLE) { |
| 472 newAudioDevices.add(AudioDevice.BLUETOOTH); |
| 473 } |
| 474 |
372 if (hasWiredHeadset) { | 475 if (hasWiredHeadset) { |
373 // If a wired headset is connected, then it is the only possible option. | 476 // If a wired headset is connected, then it is the only possible option. |
374 audioDevices.add(AudioDevice.WIRED_HEADSET); | 477 newAudioDevices.add(AudioDevice.WIRED_HEADSET); |
375 } else { | 478 } else { |
376 // No wired headset, hence the audio-device list can contain speaker | 479 // No wired headset, hence the audio-device list can contain speaker |
377 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). | 480 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). |
378 audioDevices.add(AudioDevice.SPEAKER_PHONE); | 481 newAudioDevices.add(AudioDevice.SPEAKER_PHONE); |
379 if (hasEarpiece()) { | 482 if (hasEarpiece()) { |
380 audioDevices.add(AudioDevice.EARPIECE); | 483 newAudioDevices.add(AudioDevice.EARPIECE); |
381 } | 484 } |
382 } | 485 } |
383 Log.d(TAG, "audioDevices: " + audioDevices); | 486 // Store state which is set to true if the device list has changed. |
384 | 487 boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices); |
385 // Switch to correct audio device given the list of available audio devices. | 488 // Update the existing audio device set. |
386 if (hasWiredHeadset) { | 489 audioDevices = newAudioDevices; |
387 setAudioDevice(AudioDevice.WIRED_HEADSET); | 490 // Correct user selected audio devices if needed. |
388 } else { | 491 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAV
AILABLE |
389 setAudioDevice(defaultAudioDevice); | 492 && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { |
| 493 // If BT is not available, it can't be the user selection. |
| 494 userSelectedAudioDevice = AudioDevice.NONE; |
390 } | 495 } |
391 } | 496 if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE)
{ |
392 | 497 // If user selected speaker phone, but then plugged wired headset then mak
e |
393 /** Called each time a new audio device has been added or removed. */ | 498 // wired headset as user selected device. |
394 private void onAudioManagerChangedState() { | 499 userSelectedAudioDevice = AudioDevice.WIRED_HEADSET; |
395 Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices + ", select
ed=" | 500 } |
396 + selectedAudioDevice); | 501 if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET
) { |
397 | 502 // If user selected wired headset, but then unplugged wired headset then m
ake |
398 // Enable the proximity sensor if there are two available audio devices | 503 // speaker phone as user selected device. |
399 // in the list. Given the current implementation, we know that the choice | 504 userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE; |
400 // will then be between EARPIECE and SPEAKER_PHONE. | |
401 if (audioDevices.size() == 2) { | |
402 AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) | |
403 && audioDevices.contains(AudioDevice.SPEAKER_PHONE)); | |
404 // Start the proximity sensor. | |
405 proximitySensor.start(); | |
406 } else if (audioDevices.size() == 1) { | |
407 // Stop the proximity sensor since it is no longer needed. | |
408 proximitySensor.stop(); | |
409 } else { | |
410 Log.e(TAG, "Invalid device list"); | |
411 } | 505 } |
412 | 506 |
413 if (onStateChangeListener != null) { | 507 // Need to start Bluetooth if it is available and user either selected it ex
plicitly or |
414 // Run callback to notify a listening client. The client can then | 508 // user did not select any output device. |
415 // use public getters to query the new state. | 509 boolean needBluetoothAudioStart = |
416 onStateChangeListener.run(); | 510 bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI
LABLE |
| 511 && (userSelectedAudioDevice == AudioDevice.NONE |
| 512 || userSelectedAudioDevice == AudioDevice.BLUETOOTH); |
| 513 |
| 514 // Need to stop Bluetooth audio if user selected different device and |
| 515 // Bluetooth SCO connection is established or in the process. |
| 516 boolean needBluetoothAudioStop = |
| 517 (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECT
ED |
| 518 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_C
ONNECTING) |
| 519 && (userSelectedAudioDevice != AudioDevice.NONE |
| 520 && userSelectedAudioDevice != AudioDevice.BLUETOOTH); |
| 521 |
| 522 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI
LABLE |
| 523 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE
CTING |
| 524 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE
CTED) { |
| 525 Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", " |
| 526 + "stop=" + needBluetoothAudioStop + ", " |
| 527 + "BT state=" + bluetoothManager.getState()); |
417 } | 528 } |
| 529 |
| 530 // Start or stop Bluetooth SCO connection given states set earlier. |
| 531 if (needBluetoothAudioStop) { |
| 532 bluetoothManager.stopScoAudio(); |
| 533 bluetoothManager.updateDevice(); |
| 534 } |
| 535 |
| 536 if (needBluetoothAudioStart && !needBluetoothAudioStop) { |
| 537 // Attempt to start Bluetooth SCO audio (takes a few second to start). |
| 538 if (!bluetoothManager.startScoAudio()) { |
| 539 // Remove BLUETOOTH from list of available devices since SCO failed. |
| 540 audioDevices.remove(AudioDevice.BLUETOOTH); |
| 541 audioDeviceSetUpdated = true; |
| 542 } |
| 543 } |
| 544 |
| 545 // Update selected audio device. |
| 546 AudioDevice newAudioDevice = selectedAudioDevice; |
| 547 |
| 548 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTE
D) { |
| 549 // If a Bluetooth is connected, then it should be used as output audio |
| 550 // device. Note that it is not sufficient that a headset is available; |
| 551 // an active SCO channel must also be up and running. |
| 552 newAudioDevice = AudioDevice.BLUETOOTH; |
| 553 } else if (hasWiredHeadset) { |
| 554 // If a wired headset is connected, but Bluetooth is not, then wired heads
et is used as |
| 555 // audio device. |
| 556 newAudioDevice = AudioDevice.WIRED_HEADSET; |
| 557 } else { |
| 558 // No wired headset and no Bluetooth, hence the audio-device list can cont
ain speaker |
| 559 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). |
| 560 // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or Audio
Device.EARPIECE |
| 561 // depending on the user's selection. |
| 562 newAudioDevice = defaultAudioDevice; |
| 563 } |
| 564 // Switch to new device but only if there has been any changes. |
| 565 if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { |
| 566 // Do the required device switch. |
| 567 setAudioDeviceInternal(newAudioDevice); |
| 568 Log.d(TAG, "New device status: " |
| 569 + "available=" + audioDevices + ", " |
| 570 + "selected=" + newAudioDevice); |
| 571 if (audioManagerEvents != null) { |
| 572 // Notify a listening client that audio device has been changed. |
| 573 audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevice
s); |
| 574 } |
| 575 } |
| 576 Log.d(TAG, "--- updateAudioDeviceState done"); |
418 } | 577 } |
419 } | 578 } |
OLD | NEW |