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

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

Issue 2501983002: Adds basic Bluetooth support to AppRTCMobile (Closed)
Patch Set: Finalized the test suite. Now contains seven tests 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,
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698