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