OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. | 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 package org.appspot.apprtc; | 11 package org.appspot.apprtc; |
12 | 12 |
13 import org.appspot.apprtc.util.AppRTCUtils; | 13 import org.appspot.apprtc.util.AppRTCUtils; |
14 | 14 |
15 import android.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 Loading... | |
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 } |
OLD | NEW |