Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(59)

Side by Side Diff: webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java

Issue 2501983002: Adds basic Bluetooth support to AppRTCMobile (Closed)
Patch Set: Final comments from magjed@ Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698