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

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

Issue 2501983002: Adds basic Bluetooth support to AppRTCMobile (Closed)
Patch Set: Removed proximity sensor 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.bluetooth.BluetoothAdapter;
16 import android.bluetooth.BluetoothDevice;
17 import android.bluetooth.BluetoothHeadset;
18 import android.bluetooth.BluetoothProfile;
15 import android.content.BroadcastReceiver; 19 import android.content.BroadcastReceiver;
16 import android.content.Context; 20 import android.content.Context;
17 import android.content.Intent; 21 import android.content.Intent;
18 import android.content.IntentFilter; 22 import android.content.IntentFilter;
19 import android.content.SharedPreferences; 23 import android.content.SharedPreferences;
20 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager;
21 import android.media.AudioManager; 25 import android.media.AudioManager;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.Process;
22 import android.preference.PreferenceManager; 29 import android.preference.PreferenceManager;
23 import android.util.Log; 30 import android.util.Log;
24 31
25 import java.util.Collections; 32 import java.util.Collections;
26 import java.util.HashSet; 33 import java.util.HashSet;
34 import java.util.List;
27 import java.util.Set; 35 import java.util.Set;
28 36
29 /** 37 // AppRTCAudioManager manages all audio related parts of the AppRTC demo.
magjed_webrtc 2016/11/28 12:59:45 We are not following the style guide strictly when
henrika_webrtc 2016/11/28 14:06:18 Acknowledged.
30 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
31 */
32 public class AppRTCAudioManager { 38 public class AppRTCAudioManager {
33 private static final String TAG = "AppRTCAudioManager"; 39 private static final String TAG = "AppRTCAudioManager";
40 private static final String TAG_BT = "AppRTCAudioManagerBT";
34 private static final String SPEAKERPHONE_AUTO = "auto"; 41 private static final String SPEAKERPHONE_AUTO = "auto";
35 private static final String SPEAKERPHONE_TRUE = "true"; 42 private static final String SPEAKERPHONE_TRUE = "true";
36 private static final String SPEAKERPHONE_FALSE = "false"; 43 private static final String SPEAKERPHONE_FALSE = "false";
37 44
38 /** 45 // Timeout interval for starting or stopping audio to a Bluetooth SCO device.
39 * AudioDevice is the names of possible audio devices that we currently 46 private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
40 * support. 47 // Maximum number of SCO connection attempts.
41 */ 48 private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
42 // TODO(henrika): add support for BLUETOOTH as well. 49
43 public enum AudioDevice { 50 // AudioDevice is the names of possible audio devices that we currently
44 SPEAKER_PHONE, 51 // support.
45 WIRED_HEADSET, 52 public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, N ONE }
46 EARPIECE, 53
54 // AudioManager state.
55 public enum AudioManagerState {
56 UNINITIALIZED,
57 PREINITIALIZED,
58 RUNNING,
59 }
60
61 // Bluetooth connection state.
62 public enum BluetoothState {
63 // Bluetooth is not available; no adapter or Bluetooth is off.
64 UNINITIALIZED,
65 // Bluetooth error happened when trying to start Bluetooth.
66 ERROR,
67 // Bluetooth SCO proxy is connected, but no connected Bluetooth headset devi ces,
68 // SCO is not started or disconnected.
69 HEADSET_UNAVAILABLE,
70 // Bluetooth SCO proxy is connected, connected Bluetooth headset present, bu t SCO is
71 // not started or disconnected.
72 HEADSET_AVAILABLE,
73 // Bluetooth audio SCO connection with remote device is closing.
74 SCO_DISCONNECTING,
75 // Bluetooth audio SCO connection with remote device is initiated.
76 SCO_CONNECTING,
77 // Bluetooth audio SCO connection with remote device is established.
78 SCO_CONNECTED
79 }
80
81 // Selected audio device change event.
82 public static interface AudioManagerEvents {
83 // Callback fired once audio device is changed or list of available audio de vices changed.
84 void onAudioDeviceChanged(
85 AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices) ;
47 } 86 }
48 87
49 private final Context apprtcContext; 88 private final Context apprtcContext;
50 private final Runnable onStateChangeListener;
51 private boolean initialized = false;
52 private AudioManager audioManager; 89 private AudioManager audioManager;
90 private final Handler handler;
91
92 private AudioManagerEvents audioManagerEvents;
93 private AudioManagerState amState;
53 private int savedAudioMode = AudioManager.MODE_INVALID; 94 private int savedAudioMode = AudioManager.MODE_INVALID;
54 private boolean savedIsSpeakerPhoneOn = false; 95 private boolean savedIsSpeakerPhoneOn = false;
55 private boolean savedIsMicrophoneMute = false; 96 private boolean savedIsMicrophoneMute = false;
97 private boolean hasWiredHeadset = false;
56 98
57 private final AudioDevice defaultAudioDevice; 99 // Default audio device; speaker phone for video calls or earpiece for audio
100 // only calls.
101 private AudioDevice defaultAudioDevice;
102
103 // Contains the currently selected audio device.
104 private AudioDevice selectedAudioDevice;
105
106 // Contains user selected audio device.
107 // TODO(henrika): always set to AudioDevice.NONE today. Add support for
108 // explicit selection based on choice by userSelectedAudioDevice.
109 private AudioDevice userSelectedAudioDevice;
58 110
59 // Contains speakerphone setting: auto, true or false 111 // Contains speakerphone setting: auto, true or false
60 private final String useSpeakerphone; 112 private final String useSpeakerphone;
61 113
62 // Proximity sensor object. It measures the proximity of an object in cm 114 // Enabled during initialization if BLUETOOTH permission is granted.
63 // relative to the view screen of a device and can therefore be used to 115 private boolean hasBluetoothPermission = false;
magjed_webrtc 2016/11/28 12:59:45 I don't think it's a good idea to cache permission
henrika_webrtc 2016/11/28 14:06:18 Acknowledged.
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 116
68 // Contains the currently selected audio device. 117 // Listener for when we have been connected or disconnected to the Headset
69 private AudioDevice selectedAudioDevice; 118 // Bluetooth profile.
119 private final BluetoothProfile.ServiceListener bluetoothServiceListener;
120
121 private BluetoothState bluetoothState;
122 private BluetoothAdapter bluetoothAdapter;
123 private BluetoothHeadset bluetoothHeadset;
124 private BluetoothDevice bluetoothDevice;
125 // Number of Bluetooth SCO connection attempts.
126 int scoConnectionAttempts;
70 127
71 // Contains a list of available audio devices. A Set collection is used to 128 // Contains a list of available audio devices. A Set collection is used to
72 // avoid duplicate elements. 129 // avoid duplicate elements.
73 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>(); 130 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
74 131
75 // Broadcast receiver for wired headset intent broadcasts. 132 // Broadcast receiver for wired headset intent broadcasts.
76 private BroadcastReceiver wiredHeadsetReceiver; 133 private BroadcastReceiver wiredHeadsetReceiver;
77 134
135 // Broadcast receiver for Bluetooth headset intent broadcasts. Detects
136 // headset changes and Bluetooth SCO state changes.
137 private final BroadcastReceiver bluetoothHeadsetReceiver;
138
78 // Callback method for changes in audio focus. 139 // Callback method for changes in audio focus.
79 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; 140 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
80 141
81 // This method is called when the proximity sensor reports a state change, 142 // Runs when the Bluetooth timeout expires. We use that timeout after calling
82 // e.g. from "NEAR to FAR" or from "FAR to NEAR". 143 // startBluetoothSco() or stopBluetoothSco() because we're not guaranteed to
83 private void onProximitySensorChangedState() { 144 // get a callback after those calls.
84 if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { 145 private final Runnable bluetoothTimeoutRunnable = new Runnable() {
85 return; 146 @Override
147 public void run() {
148 bluetoothTimeout();
149 }
150 };
151
152 // Receiver which handles changes in wired headset availability.
153 private class WiredHeadsetReceiver extends BroadcastReceiver {
154 private static final int STATE_UNPLUGGED = 0;
155 private static final int STATE_PLUGGED = 1;
156 private static final int HAS_NO_MIC = 0;
157 private static final int HAS_MIC = 1;
158
159 @Override
160 public void onReceive(Context context, Intent intent) {
161 int state = intent.getIntExtra("state", STATE_UNPLUGGED);
162 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
163 String name = intent.getStringExtra("name");
164 Log.d(TAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": "
165 + "a=" + intent.getAction() + ", s="
166 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m="
167 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb="
168 + isInitialStickyBroadcast());
169 hasWiredHeadset = (state == STATE_PLUGGED);
170 updateAudioDeviceState();
171 }
172 };
173
174 // Intent broadcast receiver which handles changes in Bluetooth device availab ility.
175 private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver {
176 @Override
177 public void onReceive(Context context, Intent intent) {
178 if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothSta te.UNINITIALIZED) {
179 return;
180 }
181 final String action = intent.getAction();
182 // Change in connection state of the Headset profile. Note that the
183 // change does not tell us anything about whether we're streaming
184 // audio to BT over SCO.
185 if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
186 final int state =
187 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.ST ATE_DISCONNECTED);
188 final BluetoothDevice device =
189 (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DE VICE);
190 Log.d(TAG_BT, "BluetoothHeadsetBroadcastReceiver.onReceive: "
191 + "a=ACTION_CONNECTION_STATE_CHANGED, "
192 + "s=" + stateToString(state) + ", "
193 + "n=" + device.getName() + ", "
194 + "sb=" + isInitialStickyBroadcast() + ", "
195 + "BT state: " + bluetoothState);
196 if (state == BluetoothHeadset.STATE_CONNECTED) {
197 scoConnectionAttempts = 0;
198 updateAudioDeviceState();
199 } else if (state == BluetoothHeadset.STATE_CONNECTING) {
200 // No action needed.
201 } else if (state == BluetoothHeadset.STATE_DISCONNECTING) {
202 // No action needed.
203 } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
204 // Bluetooth is probably powered off during the call.
205 stopBluetoothSco();
206 updateAudioDeviceState();
207 }
208 // Change in the audio (SCO) connection state of the Headset profile.
209 } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
210 final int state = intent.getIntExtra(
211 BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNEC TED);
212 final BluetoothDevice device =
213 (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DE VICE);
214 Log.d(TAG_BT, "BluetoothHeadsetBroadcastReceiver.onReceive: "
215 + "a=ACTION_AUDIO_STATE_CHANGED, "
216 + "s=" + stateToString(state) + ", "
217 + "n=" + device.getName() + ", "
218 + "sb=" + isInitialStickyBroadcast() + ", "
219 + "BT state: " + bluetoothState);
220 if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
221 cancelBluetoothTimer();
222 if (bluetoothState == BluetoothState.SCO_CONNECTING) {
223 bluetoothState = BluetoothState.SCO_CONNECTED;
224 scoConnectionAttempts = 0;
225 updateAudioDeviceState();
226 } else {
227 Log.w(TAG_BT, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECT ED");
228 }
229 } else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
230 // No action needed.
231 } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
232 if (isInitialStickyBroadcast()) {
233 Log.d(TAG_BT, "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadc ast.");
234 return;
235 }
236 stopBluetoothSco();
237 updateAudioDeviceState();
238 }
239 }
240 }
241 };
242
243 // Implementation of an interface that notifies BluetoothProfile IPC clients w hen they have been
244 // connected to or disconnected from the service.
245 private class BluetoothServiceListener implements BluetoothProfile.ServiceList ener {
246 @Override
247 // Called to notify the client when the proxy object has been connected to t he service.
248 public void onServiceConnected(int profile, BluetoothProfile proxy) {
249 if (profile != BluetoothProfile.HEADSET || bluetoothState == BluetoothStat e.UNINITIALIZED) {
250 return;
251 }
252 Log.d(TAG_BT, "BluetoothServiceListener.onServiceConnected: BT state=" + b luetoothState);
253 // Android only supports one connected Bluetooth Headset at a time.
254 bluetoothHeadset = (BluetoothHeadset) proxy;
255 // Update BT state and list of available audio devices.
256 updateAudioDeviceState();
86 } 257 }
87 258
88 // The proximity sensor should only be activated when there are exactly two 259 @Override
89 // available audio devices. 260 // Called to notify the client that this proxy object has been disconnected from the service.
90 if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.Aud ioDevice.EARPIECE) 261 public void onServiceDisconnected(int profile) {
91 && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) { 262 if (profile != BluetoothProfile.HEADSET || bluetoothState == BluetoothStat e.UNINITIALIZED) {
92 if (proximitySensor.sensorReportsNearState()) { 263 return;
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 } 264 }
265 Log.d(TAG_BT, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
266 // stopBluetoothSco();
267 bluetoothHeadset = null;
268 // bluetoothDevice = null;
269 bluetoothState = BluetoothState.HEADSET_UNAVAILABLE;
270 // Update BT state and list of available audio devices.
271 updateAudioDeviceState();
101 } 272 }
102 } 273 }
103 274
104 /** Construction */ 275 // Construction.
105 static AppRTCAudioManager create(Context context, Runnable deviceStateChangeLi stener) { 276 static AppRTCAudioManager create(Context context) {
106 return new AppRTCAudioManager(context, deviceStateChangeListener); 277 return new AppRTCAudioManager(context);
107 } 278 }
108 279
109 private AppRTCAudioManager(Context context, Runnable deviceStateChangeListener ) { 280 private AppRTCAudioManager(Context context) {
281 Log.d(TAG, "ctor");
110 apprtcContext = context; 282 apprtcContext = context;
111 onStateChangeListener = deviceStateChangeListener;
112 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC E)); 283 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC E));
284 wiredHeadsetReceiver = new WiredHeadsetReceiver();
285 bluetoothServiceListener = new BluetoothServiceListener();
286 bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver();
287 amState = AudioManagerState.UNINITIALIZED;
288
289 final HandlerThread handlerThread = new HandlerThread(TAG);
290 handlerThread.start();
magjed_webrtc 2016/11/28 12:59:45 It looks like you are only using this thread for a
henrika_webrtc 2016/11/28 14:06:18 Will see if I can resolve in ant alternative way.
magjed_webrtc 2016/11/29 19:59:52 I thought you would do more than just a timeout. J
291 handler = new Handler(handlerThread.getLooper());
113 292
114 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref erences(context); 293 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref erences(context);
115 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre f_speakerphone_key), 294 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre f_speakerphone_key),
116 context.getString(R.string.pref_speakerphone_default)); 295 context.getString(R.string.pref_speakerphone_default));
117 296 Log.d(TAG, "useSpeakerphone: " + useSpeakerphone);
118 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { 297 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
119 defaultAudioDevice = AudioDevice.EARPIECE; 298 defaultAudioDevice = AudioDevice.EARPIECE;
120 } else { 299 } else {
121 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; 300 defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
122 } 301 }
302 Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice);
303 AppRTCUtils.logDeviceInfo(TAG);
123 304
124 // Create and initialize the proximity sensor. 305 // Check if this process has the BLUETOOTH permission or not.
125 // Tablet devices (e.g. Nexus 7) does not support proximity sensors. 306 hasBluetoothPermission = hasPermission(android.Manifest.permission.BLUETOOTH );
126 // Note that, the sensor will not be active until start() has been called. 307 Log.d(TAG_BT, "hasBluetoothPermission: " + hasBluetoothPermission);
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);
136 } 308 }
137 309
138 public void init() { 310 public void start(AudioManagerEvents audioManagerEvents) {
139 Log.d(TAG, "init"); 311 Log.d(TAG, "start");
140 if (initialized) { 312 if (amState == AudioManagerState.RUNNING) {
313 Log.e(TAG, "AudioManager is already active");
141 return; 314 return;
142 } 315 }
316 // TODO(henrika): perhaps call new method called preInitAudio() here if UNIN ITIALIZED.
143 317
144 // Store current audio state so we can restore it when close() is called. 318 Log.d(TAG, "AudioManager starts...");
319 this.audioManagerEvents = audioManagerEvents;
320 amState = AudioManagerState.RUNNING;
321
322 // Store current audio state so we can restore it when stop() is called.
145 savedAudioMode = audioManager.getMode(); 323 savedAudioMode = audioManager.getMode();
146 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); 324 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
147 savedIsMicrophoneMute = audioManager.isMicrophoneMute(); 325 savedIsMicrophoneMute = audioManager.isMicrophoneMute();
326 hasWiredHeadset = hasWiredHeadset();
148 327
149 // Create an AudioManager.OnAudioFocusChangeListener instance. 328 // Create an AudioManager.OnAudioFocusChangeListener instance.
150 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { 329 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
151 // Called on the listener to notify if the audio focus for this listener h as been changed. 330 // 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, 331 // 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 332 // and whether that loss is transient, or whether the new focus holder wil l hold it for an
154 // unknown amount of time. 333 // unknown amount of time.
155 // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains 334 // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains
156 // logging for now. 335 // logging for now.
157 @Override 336 @Override
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
192 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 371 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
193 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 372 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
194 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams"); 373 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams");
195 } else { 374 } else {
196 Log.e(TAG, "Audio focus request failed"); 375 Log.e(TAG, "Audio focus request failed");
197 } 376 }
198 377
199 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is 378 // 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 379 // required to be in this mode when playout and/or recording starts for
201 // best possible VoIP performance. 380 // best possible VoIP performance.
202 // TODO(henrika): we migh want to start with RINGTONE mode here instead.
203 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 381 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
204 382
205 // Always disable microphone mute during a WebRTC call. 383 // Always disable microphone mute during a WebRTC call.
206 setMicrophoneMute(false); 384 setMicrophoneMute(false);
207 385
386 // Set initial device states.
387 userSelectedAudioDevice = AudioDevice.NONE;
388 selectedAudioDevice = AudioDevice.NONE;
389 audioDevices.clear();
390
391 // Initialize and start Bluetooth if a BT device is available or initiate
392 // detection of new (enabled) BT devices.
393 bluetoothState = BluetoothState.UNINITIALIZED;
394 startBluetooth();
395
208 // Do initial selection of audio device. This setting can later be changed 396 // 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 397 // either by adding/removing a BT or wired headset or by covering/uncovering
210 // proximity sensor. 398 // the proximity sensor.
211 updateAudioDeviceState(hasWiredHeadset()); 399 updateAudioDeviceState();
212 400
213 // Register receiver for broadcast intents related to adding/removing a 401 // Register receiver for broadcast intents related to adding/removing a
214 // wired headset (Intent.ACTION_HEADSET_PLUG). 402 // wired headset.
215 registerForWiredHeadsetIntentBroadcast(); 403 registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSE T_PLUG));
216 404
217 initialized = true; 405 IntentFilter bluetoothHeadsetFilter = new IntentFilter();
406 // Register receiver for change in connection state of the Headset profile.
407 bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CH ANGED);
408 // Register receiver for change in audio connection state of the Headset pro file.
409 bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED );
410 registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
411 Log.d(TAG, "AudioManager started");
218 } 412 }
219 413
220 public void close() { 414 public void stop() {
221 Log.d(TAG, "close"); 415 Log.d(TAG, "stop");
222 if (!initialized) { 416 if (amState != AudioManagerState.RUNNING) {
417 Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState);
223 return; 418 return;
224 } 419 }
420 amState = AudioManagerState.UNINITIALIZED;
225 421
226 unregisterForWiredHeadsetIntentBroadcast(); 422 // Cancel Bluetooth related tasks.
423 unregisterReceiver(bluetoothHeadsetReceiver);
424 unregisterReceiver(wiredHeadsetReceiver);
425 stopBluetooth();
227 426
228 // Restore previously stored audio states. 427 // Restore previously stored audio states.
229 setSpeakerphoneOn(savedIsSpeakerPhoneOn); 428 setSpeakerphoneOn(savedIsSpeakerPhoneOn);
230 setMicrophoneMute(savedIsMicrophoneMute); 429 setMicrophoneMute(savedIsMicrophoneMute);
231 audioManager.setMode(savedAudioMode); 430 audioManager.setMode(savedAudioMode);
232 431
233 // Abandon audio focus. Gives the previous focus owner, if any, focus. 432 // Abandon audio focus. Gives the previous focus owner, if any, focus.
234 audioManager.abandonAudioFocus(audioFocusChangeListener); 433 audioManager.abandonAudioFocus(audioFocusChangeListener);
235 audioFocusChangeListener = null; 434 audioFocusChangeListener = null;
236 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); 435 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams");
237 436
238 if (proximitySensor != null) { 437 audioManagerEvents = null;
239 proximitySensor.stop(); 438 Log.d(TAG, "AudioManager stopped");
240 proximitySensor = null;
241 }
242
243 initialized = false;
244 } 439 }
245 440
246 /** Changes selection of the currently active audio device. */ 441 // Changes selection of the currently active audio device.
247 public void setAudioDevice(AudioDevice device) { 442 private void setAudioDeviceInternal(AudioDevice device) {
248 Log.d(TAG, "setAudioDevice(device=" + device + ")"); 443 Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")");
249 AppRTCUtils.assertIsTrue(audioDevices.contains(device)); 444 AppRTCUtils.assertIsTrue(audioDevices.contains(device));
250 445
251 switch (device) { 446 switch (device) {
252 case SPEAKER_PHONE: 447 case SPEAKER_PHONE:
253 setSpeakerphoneOn(true); 448 setSpeakerphoneOn(true);
254 selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
255 break; 449 break;
256 case EARPIECE: 450 case EARPIECE:
257 setSpeakerphoneOn(false); 451 setSpeakerphoneOn(false);
258 selectedAudioDevice = AudioDevice.EARPIECE;
259 break; 452 break;
260 case WIRED_HEADSET: 453 case WIRED_HEADSET:
261 setSpeakerphoneOn(false); 454 setSpeakerphoneOn(false);
262 selectedAudioDevice = AudioDevice.WIRED_HEADSET; 455 break;
456 case BLUETOOTH:
457 setSpeakerphoneOn(false);
263 break; 458 break;
264 default: 459 default:
265 Log.e(TAG, "Invalid audio device selection"); 460 Log.e(TAG, "Invalid audio device selection");
266 break; 461 break;
267 } 462 }
268 onAudioManagerChangedState(); 463 selectedAudioDevice = device;
269 } 464 }
270 465
271 /** Returns current set of available/selectable audio devices. */ 466 // Changes default audio device.
272 public Set<AudioDevice> getAudioDevices() { 467 // TODO(henrika): add usage of this method in the AppRTCMobile client.
468 public synchronized void setDefaultAudioDevice(AudioDevice defaultDevice) {
469 switch (defaultDevice) {
470 case SPEAKER_PHONE:
471 defaultAudioDevice = defaultDevice;
472 break;
473 case EARPIECE:
474 if (hasEarpiece()) {
475 defaultAudioDevice = defaultDevice;
476 } else {
477 defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
478 }
479 break;
480 default:
481 Log.e(TAG, "Invalid default audio device selection");
482 break;
483 }
484 Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
485 updateAudioDeviceState();
486 }
487
488 // Changes selection of the currently active audio device.
489 public synchronized void selectAudioDevice(AudioDevice device) {
490 if (!audioDevices.contains(device)) {
491 Log.e(TAG, "Can not select " + device + " from available " + audioDevices) ;
492 }
493 userSelectedAudioDevice = device;
494 updateAudioDeviceState();
495 }
496
497 // Returns current set of available/selectable audio devices.
498 public synchronized Set<AudioDevice> getAudioDevices() {
273 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); 499 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
274 } 500 }
275 501
276 /** Returns the currently selected audio device. */ 502 // Returns the currently selected audio device.
277 public AudioDevice getSelectedAudioDevice() { 503 public synchronized AudioDevice getSelectedAudioDevice() {
278 return selectedAudioDevice; 504 return selectedAudioDevice;
279 } 505 }
280 506
281 /** 507 // Helper method for receiver registration.
282 * Registers receiver for the broadcasted intent when a wired headset is 508 private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
283 * plugged in or unplugged. The received intent will have an extra 509 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 } 510 }
326 511
327 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ 512 // Helper method for unregistration of an existing receiver.
328 private void unregisterForWiredHeadsetIntentBroadcast() { 513 private void unregisterReceiver(BroadcastReceiver receiver) {
329 apprtcContext.unregisterReceiver(wiredHeadsetReceiver); 514 apprtcContext.unregisterReceiver(receiver);
330 wiredHeadsetReceiver = null;
331 } 515 }
332 516
333 /** Sets the speaker phone mode. */ 517 // Sets the speaker phone mode.
334 private void setSpeakerphoneOn(boolean on) { 518 private void setSpeakerphoneOn(boolean on) {
335 boolean wasOn = audioManager.isSpeakerphoneOn(); 519 boolean wasOn = audioManager.isSpeakerphoneOn();
336 if (wasOn == on) { 520 if (wasOn == on) {
337 return; 521 return;
338 } 522 }
339 audioManager.setSpeakerphoneOn(on); 523 audioManager.setSpeakerphoneOn(on);
340 } 524 }
341 525
342 /** Sets the microphone mute state. */ 526 // Sets the microphone mute state.
343 private void setMicrophoneMute(boolean on) { 527 private void setMicrophoneMute(boolean on) {
344 boolean wasMuted = audioManager.isMicrophoneMute(); 528 boolean wasMuted = audioManager.isMicrophoneMute();
345 if (wasMuted == on) { 529 if (wasMuted == on) {
346 return; 530 return;
347 } 531 }
348 audioManager.setMicrophoneMute(on); 532 audioManager.setMicrophoneMute(on);
349 } 533 }
350 534
351 /** Gets the current earpiece state. */ 535 // Starts all Bluetooth related tasks.
536 void startBluetooth() {
537 Log.d(TAG_BT, "startBluetooth");
538 if (!hasBluetoothPermission) {
539 Log.w(TAG_BT, "Process lacks BLUETOOTH permission");
540 return;
541 }
542 if (bluetoothState != BluetoothState.UNINITIALIZED) {
543 Log.w(TAG_BT, "Invalid BT state");
544 return;
545 }
546 bluetoothHeadset = null;
547 bluetoothDevice = null;
548 scoConnectionAttempts = 0;
549 // Get a handle to the default local Bluetooth adapter.
550 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
551 if (bluetoothAdapter == null) {
552 Log.w(TAG_BT, "Device does not support Bluetooth");
553 return;
554 }
555 // Ensure that the device supports use of BT SCO audio for off call use case s.
556 if (!audioManager.isBluetoothScoAvailableOffCall()) {
557 Log.e(TAG_BT, "Bluetooth SCO audio is not available off call");
558 return;
559 }
560 logBluetoothAdapterInfo(bluetoothAdapter);
561 // Establish a connection to the HEADSET profile (includes both Bluetooth He adset and
562 // Hands-Free) proxy object and install a listener.
563 if (!bluetoothAdapter.getProfileProxy(
564 apprtcContext, bluetoothServiceListener, BluetoothProfile.HEADSET)) {
565 Log.e(TAG_BT, "BluetoothAdapter.getProfileProxy(HEADSET) failed");
566 return;
567 }
568 Log.d(TAG_BT, "HEADSET profile state: "
569 + stateToString(bluetoothAdapter.getProfileConnectionState(Bluetooth Profile.HEADSET)));
570 Log.d(TAG_BT, "Bluetooth proxy for headset profile has started");
571 bluetoothState = BluetoothState.HEADSET_UNAVAILABLE;
572 }
573
574 // Stops all Bluetooth tasks.
575 void stopBluetooth() {
576 Log.d(TAG_BT, "stopBluetooth: BT state=" + bluetoothState);
577 if (bluetoothAdapter != null) {
578 // Stop BT SCO connection with remote device if needed.
579 stopBluetoothSco();
580 // Close down remaining BT resources.
581 if (bluetoothState != BluetoothState.UNINITIALIZED) {
582 cancelBluetoothTimer();
583 if (bluetoothHeadset != null) {
584 bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetooth Headset);
585 bluetoothHeadset = null;
586 }
587 bluetoothAdapter = null;
588 bluetoothDevice = null;
589 bluetoothState = BluetoothState.UNINITIALIZED;
590 }
591 }
592 }
593
594 // Called when start of the BT SCO channel takes too long time. Usually
595 // happens when the BT device has been turned on during an ongoing call.
596 private void bluetoothTimeout() {
597 if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothState .UNINITIALIZED
598 || bluetoothHeadset == null) {
599 return;
600 }
601 Log.d(TAG_BT, "bluetoothTimeout: BT state=" + bluetoothState);
602 if (bluetoothState != BluetoothState.SCO_CONNECTING) {
603 return;
604 }
605 // Bluetooth SCO should be connecting; check the latest result.
606 boolean scoConnected = false;
607 List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
608 if (devices.size() > 0) {
609 bluetoothDevice = devices.get(0);
610 if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) {
611 Log.d(TAG_BT, "SCO connected with " + bluetoothDevice.getName());
612 scoConnected = true;
613 } else {
614 Log.d(TAG_BT, "SCO is not connected with " + bluetoothDevice.getName());
615 }
616 }
617
618 if (scoConnected) {
619 // We thought BT had timed out, but it's actually on; updating state.
620 bluetoothState = BluetoothState.SCO_CONNECTED;
621 scoConnectionAttempts = 0;
622 updateAudioDeviceState();
623 } else {
624 // Give up and "cancel" our request by calling stopBluetoothSco().
625 Log.w(TAG_BT, "BT failed to connect after timeout");
626 stopBluetoothSco();
627 updateAudioDeviceState();
628 }
629 }
630
631 private void startBluetoothTimer() {
632 Log.d(TAG_BT, "startBluetoothTimer");
633 handler.postDelayed(bluetoothTimeoutRunnable, BLUETOOTH_SCO_TIMEOUT_MS);
634 }
635
636 private void cancelBluetoothTimer() {
637 Log.d(TAG_BT, "cancelBluetoothTimer");
638 handler.removeCallbacks(bluetoothTimeoutRunnable);
639 }
640
641 // Starts Bluetooth SCO connection with remote device.
642 // Note that the phone application always has the priority on the usage of the SCO connection
643 // for telephony. If this method is called while the phone is in call it will be ignored.
644 // Similarly, if a call is received or sent while an application is using the SCO connection,
645 // the connection will be lost for the application and NOT returned automatica lly when the call
646 // ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a
647 // virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_M R2 only a raw SCO
648 // audio connection is established.
649 // TODO(henrika): should we add support for virtual voice call to BT headset a lso for JBMR2 and
650 // higher. It might be required to initiates a virtual voice call since many d evices do not
651 // accept SCO audio without a "call".
652 private void startBluetoothSco() {
653 if (bluetoothState != BluetoothState.HEADSET_AVAILABLE) {
654 return;
655 }
656 Log.d(TAG_BT, "startBluetoothSco: BT state=" + bluetoothState + ", "
657 + "attempt: " + scoConnectionAttempts + ", "
658 + "SCO is on: " + audioManager.isBluetoothScoOn());
659 // The SCO connection establishment can take several seconds, hence we canno t rely on the
660 // connection to be available when the method returns but instead register t o receive the
661 // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AU DIO_STATE_CONNECTED.
662 bluetoothState = BluetoothState.SCO_CONNECTING;
663 audioManager.startBluetoothSco();
664 scoConnectionAttempts++;
665 startBluetoothTimer();
666 }
667
668 // Stops Bluetooth SCO connection with remote device.
669 private void stopBluetoothSco() {
670 if (bluetoothState != BluetoothState.SCO_CONNECTING
671 && bluetoothState != BluetoothState.SCO_CONNECTED) {
672 return;
673 }
674 Log.d(TAG_BT, "stopBluetoothSco: BT state=" + bluetoothState + ", "
675 + "SCO is on: " + audioManager.isBluetoothScoOn());
676 cancelBluetoothTimer();
677 audioManager.stopBluetoothSco();
678 bluetoothState = BluetoothState.SCO_DISCONNECTING;
679 }
680
681 // Use the BluetoothHeadset proxy object (controls the Bluetooth Headset Servi ce via IPC) to
682 // update the list of connected devices for the HEADSET profile.
683 private void updateBluetoothDevices() {
684 if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothState .UNINITIALIZED
685 || bluetoothHeadset == null) {
686 return;
687 }
688 Log.d(TAG_BT, "updateBluetoothDevices");
689 // Update the Bluetooth device list for the headset profile.
690 // The BluetoothDevice class is just a thin wrapper for a Bluetooth hardware address.
691 List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
692 if (devices.size() == 0) {
693 bluetoothDevice = null;
694 } else {
695 bluetoothDevice = devices.get(0);
696 }
697 if (bluetoothDevice == null) {
698 bluetoothState = BluetoothState.HEADSET_UNAVAILABLE;
699 Log.d(TAG_BT, "No connected bluetooth headset");
700 } else {
701 bluetoothState = BluetoothState.HEADSET_AVAILABLE;
702 Log.d(TAG_BT, "Connected bluetooth headset: "
703 + "name=" + bluetoothDevice.getName() + ", "
704 + "state=" + stateToString(bluetoothHeadset.getConnectionState(blu etoothDevice))
705 + ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevi ce));
706 }
707 Log.d(TAG_BT, "Updated Bluetooth state: " + bluetoothState);
708 }
709
710 // Gets the current earpiece state.
352 private boolean hasEarpiece() { 711 private boolean hasEarpiece() {
353 return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEA TURE_TELEPHONY); 712 return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEA TURE_TELEPHONY);
354 } 713 }
355 714
356 /** 715 // Checks whether a wired headset is connected or not.
357 * Checks whether a wired headset is connected or not. 716 // 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 717 // the wired headset as audio routing depends on other conditions. We
359 * the wired headset as audio routing depends on other conditions. We 718 // 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 719 // wired headset.
361 * wired headset.
362 */
363 @Deprecated 720 @Deprecated
364 private boolean hasWiredHeadset() { 721 private boolean hasWiredHeadset() {
365 return audioManager.isWiredHeadsetOn(); 722 return audioManager.isWiredHeadsetOn();
366 } 723 }
367 724
368 /** Update list of possible audio devices and make new device selection. */ 725 // Update list of possible audio devices and make new device selection.
369 private void updateAudioDeviceState(boolean hasWiredHeadset) { 726 private void updateAudioDeviceState() {
370 // Update the list of available audio devices. 727 Log.d(TAG, "--- updateAudioDeviceState: "
371 audioDevices.clear(); 728 + "wired headset=" + hasWiredHeadset + ", "
729 + "BT state=" + bluetoothState);
730 Log.d(TAG, "Device status: "
731 + "available=" + audioDevices + ", "
732 + "selected=" + selectedAudioDevice + ", "
733 + "user selected=" + userSelectedAudioDevice);
734
735 // Update Bluetooth device list.
736 if (bluetoothState == BluetoothState.HEADSET_AVAILABLE
737 || bluetoothState == BluetoothState.HEADSET_UNAVAILABLE
738 || bluetoothState == BluetoothState.SCO_DISCONNECTING) {
739 updateBluetoothDevices();
740 }
741
742 // Update the set of available audio devices.
743 Set<AudioDevice> newAudioDevices = new HashSet<>();
744
745 if (bluetoothState == BluetoothState.SCO_CONNECTED
746 || bluetoothState == BluetoothState.SCO_CONNECTING
747 || bluetoothState == BluetoothState.HEADSET_AVAILABLE) {
748 newAudioDevices.add(AudioDevice.BLUETOOTH);
749 }
372 if (hasWiredHeadset) { 750 if (hasWiredHeadset) {
373 // If a wired headset is connected, then it is the only possible option. 751 // If a wired headset is connected, then it is the only possible option.
374 audioDevices.add(AudioDevice.WIRED_HEADSET); 752 newAudioDevices.add(AudioDevice.WIRED_HEADSET);
375 } else { 753 } else {
376 // No wired headset, hence the audio-device list can contain speaker 754 // No wired headset, hence the audio-device list can contain speaker
377 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). 755 // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
378 audioDevices.add(AudioDevice.SPEAKER_PHONE); 756 newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
379 if (hasEarpiece()) { 757 if (hasEarpiece()) {
380 audioDevices.add(AudioDevice.EARPIECE); 758 newAudioDevices.add(AudioDevice.EARPIECE);
381 } 759 }
382 } 760 }
383 Log.d(TAG, "audioDevices: " + audioDevices); 761
384 762 // Update the existing audio device set if needed.
385 // Switch to correct audio device given the list of available audio devices. 763 boolean audioDeviceSetUpdated =
386 if (hasWiredHeadset) { 764 !audioDevices.containsAll(newAudioDevices) || !newAudioDevices.containsA ll(audioDevices);
387 setAudioDevice(AudioDevice.WIRED_HEADSET); 765 if (audioDeviceSetUpdated) {
388 } else { 766 audioDevices.clear();
389 setAudioDevice(defaultAudioDevice); 767 audioDevices.addAll(newAudioDevices);
390 } 768 }
391 } 769
392 770 // Correct user selected audio devices if needed.
393 /** Called each time a new audio device has been added or removed. */ 771 if (bluetoothState == BluetoothState.HEADSET_UNAVAILABLE
394 private void onAudioManagerChangedState() { 772 && userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
395 Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices + ", select ed=" 773 // If BT is not available, it can't be the user selection.
396 + selectedAudioDevice); 774 userSelectedAudioDevice = AudioDevice.NONE;
397 775 }
398 // Enable the proximity sensor if there are two available audio devices 776 if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) {
399 // in the list. Given the current implementation, we know that the choice 777 // If user selected speaker phone, but then plugged wired headset then mak e
400 // will then be between EARPIECE and SPEAKER_PHONE. 778 // wired headset as user selected device.
401 if (audioDevices.size() == 2) { 779 userSelectedAudioDevice = AudioDevice.WIRED_HEADSET;
402 AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) 780 }
403 && audioDevices.contains(AudioDevice.SPEAKER_PHONE)); 781 if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET ) {
404 // Start the proximity sensor. 782 // If user selected wired headset, but then unplugged wired headset then m ake
405 proximitySensor.start(); 783 // speaker phone as user selected device.
406 } else if (audioDevices.size() == 1) { 784 userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
407 // Stop the proximity sensor since it is no longer needed. 785 }
408 proximitySensor.stop(); 786
409 } else { 787 // Need to start Bluetooth if it is available and user either selected it ex plicitly or
410 Log.e(TAG, "Invalid device list"); 788 // user did not select any output device.
411 } 789 boolean needBluetoothStart = bluetoothState == BluetoothState.HEADSET_AVAILA BLE
412 790 && (userSelectedAudioDevice == AudioDevice.NONE
413 if (onStateChangeListener != null) { 791 || userSelectedAudioDevice == AudioDevice.BLUETOOTH);
414 // Run callback to notify a listening client. The client can then 792
415 // use public getters to query the new state. 793 // Need to stop Bluetooth if user selected different device and Bluetooth SC O connection is
416 onStateChangeListener.run(); 794 // established or in the process.
795 boolean needBluetoothStop = (bluetoothState == BluetoothState.SCO_CONNECTED
796 || bluetoothState == BluetoothState.SCO_CONN ECTING)
797 && (userSelectedAudioDevice != AudioDevice.NONE
798 && userSelectedAudioDevice != AudioDevice.BLUETOOTH);
799
800 if (bluetoothState == BluetoothState.HEADSET_AVAILABLE
801 || bluetoothState == BluetoothState.SCO_CONNECTING
802 || bluetoothState == BluetoothState.SCO_CONNECTED) {
803 Log.d(TAG_BT, "Need BT: start=" + needBluetoothStart + ", "
804 + "stop=" + needBluetoothStop + ", "
805 + "BT state=" + bluetoothState);
806 }
807
808 // Start/stop Bluetooth SCO connection
809 if (needBluetoothStop) {
810 stopBluetoothSco();
811 updateBluetoothDevices();
812 }
813
814 if (needBluetoothStart && !needBluetoothStop) {
815 if (scoConnectionAttempts < MAX_SCO_CONNECTION_ATTEMPTS) {
816 // Starts BT SCO channel and waits for ACTION_AUDIO_STATE_CHANGED.
817 Log.d(TAG_BT, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_C HANGED...");
818 startBluetoothSco();
819 } else {
820 Log.e(TAG_BT, "BT SCO connection fails - no more attempts");
821 audioDevices.remove(AudioDevice.BLUETOOTH);
822 audioDeviceSetUpdated = true;
823 }
824 }
825
826 // Update selected audio device.
827 AudioDevice newAudioDevice = selectedAudioDevice;
828
829 if (bluetoothState == BluetoothState.SCO_CONNECTED) {
830 // If a Bluetooth is connected, then it should be used as output audio dev ice.
831 newAudioDevice = AudioDevice.BLUETOOTH;
832 } else if (hasWiredHeadset) {
833 // If a wired headset is connected, but Bluetooth is not, then wired heads et is used as
834 // output device.
835 newAudioDevice = AudioDevice.WIRED_HEADSET;
836 } else {
837 // No wired headset and no Bluetooth, hence the audio-device list can cont ain speaker
838 // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
839 // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or Audio Device.EARPIECE
840 // depending on the user's selection.
841 newAudioDevice = defaultAudioDevice;
842 }
843 // Switch to new device but only if there has been any changes.
844 if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
845 // Do the required device switch.
846 setAudioDeviceInternal(newAudioDevice);
847 Log.d(TAG, "New device status: "
848 + "available=" + audioDevices + ", "
849 + "selected=" + newAudioDevice);
850 if (audioManagerEvents != null) {
851 // Notify a listening client that audio device has been changed.
852 audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevice s);
853 }
854 }
855 Log.d(TAG, "--- updateAudioDeviceState done");
856 }
857
858 // Checks if the process has as specified permission or not.
859 private boolean hasPermission(String permission) {
860 return apprtcContext.checkPermission(permission, Process.myPid(), Process.my Uid())
861 == PackageManager.PERMISSION_GRANTED;
862 }
863
864 private String stateToString(int state) {
865 String stateString;
866 switch (state) {
867 case BluetoothAdapter.STATE_DISCONNECTED:
868 // The profile is in disconnected state.
869 stateString = "DISCONNECTED";
870 break;
871 case BluetoothAdapter.STATE_CONNECTED:
872 // The profile is in connected state.
873 stateString = "CONNECTED";
874 break;
875 case BluetoothAdapter.STATE_CONNECTING:
876 // The profile is in connecting state.
877 stateString = "CONNECTING";
878 break;
879 case BluetoothAdapter.STATE_DISCONNECTING:
880 // The profile is in disconnecting state.
881 stateString = "DISCONNECTING";
882 break;
883 case BluetoothAdapter.STATE_OFF:
884 // Indicates the local Bluetooth adapter is off.
885 stateString = "OFF";
886 break;
887 case BluetoothAdapter.STATE_ON:
888 // Indicates the local Bluetooth adapter is on, and ready for use.
889 stateString = "ON";
890 break;
891 case BluetoothAdapter.STATE_TURNING_OFF:
892 // Indicates the local Bluetooth adapter is turning off. Local clients s hould immediately
893 // attempt graceful disconnection of any remote links.
894 stateString = "TURNING_OFF";
895 break;
896 case BluetoothAdapter.STATE_TURNING_ON:
897 // Indicates the local Bluetooth adapter is turning on. However local cl ients should wait
898 // for STATE_ON before attempting to use the adapter.
899 stateString = "TURNING_ON";
900 break;
901 default:
902 stateString = "INVALID";
903 break;
904 }
905 return stateString;
906 }
907
908 // Logs the state of the local Bluetooth adapter.
909 private void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
910 Log.d(TAG_BT, "BluetoothAdapter: "
911 + "enabled=" + localAdapter.isEnabled() + ", "
912 + "state=" + stateToString(localAdapter.getState()) + ", "
913 + "name=" + localAdapter.getName() + ", "
914 + "address=" + localAdapter.getAddress());
915 // Log the set of BluetoothDevice objects that are bonded (paired) to the lo cal adapter.
916 Set<BluetoothDevice> pairedDevices = localAdapter.getBondedDevices();
917 if (pairedDevices.size() > 0) {
918 Log.d(TAG_BT, "paired devices:");
919 for (BluetoothDevice device : pairedDevices) {
920 Log.d(TAG_BT, " name=" + device.getName() + ", address=" + device.getAdd ress());
921 }
417 } 922 }
418 } 923 }
419 } 924 }
OLDNEW
« no previous file with comments | « webrtc/examples/androidapp/AndroidManifest.xml ('k') | webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698