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

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

Issue 2501983002: Adds basic Bluetooth support to AppRTCMobile (Closed)
Patch Set: Cleaned up AppRTCBluetoothManager.java 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.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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698