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