OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. | |
3 * | |
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 | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 package org.appspot.apprtc; | |
12 | |
13 import org.appspot.apprtc.util.AppRTCUtils; | |
14 | |
15 import android.bluetooth.BluetoothAdapter; | |
16 import android.bluetooth.BluetoothDevice; | |
17 import android.bluetooth.BluetoothHeadset; | |
18 import android.bluetooth.BluetoothProfile; | |
19 import android.content.BroadcastReceiver; | |
20 import android.content.Context; | |
21 import android.content.Intent; | |
22 import android.content.IntentFilter; | |
23 import android.content.pm.PackageManager; | |
24 import android.media.AudioManager; | |
25 import android.os.Handler; | |
26 import android.os.Looper; | |
27 import android.os.Process; | |
28 import android.util.Log; | |
29 | |
30 import org.webrtc.ThreadUtils; | |
31 | |
32 import java.util.List; | |
33 import java.util.Set; | |
34 | |
35 /** | |
36 * AppRTCProximitySensor manages functions related to Bluetoth devices in the | |
37 * AppRTC demo. | |
38 */ | |
39 public class AppRTCBluetoothManager { | |
40 private static final String TAG = "AppRTCBluetoothManager"; | |
41 | |
42 // Timeout interval for starting or stopping audio to a Bluetooth SCO device. | |
43 private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000; | |
44 // Maximum number of SCO connection attempts. | |
45 private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2; | |
46 | |
47 // Bluetooth connection state. | |
48 public enum State { | |
49 // Bluetooth is not available; no adapter or Bluetooth is off. | |
50 UNINITIALIZED, | |
51 // Bluetooth error happened when trying to start Bluetooth. | |
52 ERROR, | |
53 // Bluetooth proxy object for the Headset profile exists, but no connected h eadset devices, | |
54 // SCO is not started or disconnected. | |
55 HEADSET_UNAVAILABLE, | |
56 // Bluetooth proxy object for the Headset profile connected, connected Bluet ooth headset | |
57 // present, but SCO is not started or disconnected. | |
58 HEADSET_AVAILABLE, | |
59 // Bluetooth audio SCO connection with remote device is closing. | |
60 SCO_DISCONNECTING, | |
61 // Bluetooth audio SCO connection with remote device is initiated. | |
62 SCO_CONNECTING, | |
63 // Bluetooth audio SCO connection with remote device is established. | |
64 SCO_CONNECTED | |
65 } | |
66 | |
67 private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.Thread Checker(); | |
68 | |
69 private final Context apprtcContext; | |
70 private final AppRTCAudioManager apprtcAudioManager; | |
71 private final AudioManager audioManager; | |
72 private final Handler handler; | |
73 | |
74 int scoConnectionAttempts; | |
75 private State bluetoothState; | |
76 private final BluetoothProfile.ServiceListener bluetoothServiceListener; | |
77 private BluetoothAdapter bluetoothAdapter; | |
78 private BluetoothHeadset bluetoothHeadset; | |
79 private BluetoothDevice bluetoothDevice; | |
80 private final BroadcastReceiver bluetoothHeadsetReceiver; | |
81 | |
82 // Runs when the Bluetooth timeout expires. We use that timeout after calling | |
83 // startScoAudio() or stopScoAudio() because we're not guaranteed to get a | |
84 // callback after those calls. | |
85 private final Runnable bluetoothTimeoutRunnable = new Runnable() { | |
86 @Override | |
87 public void run() { | |
88 bluetoothTimeout(); | |
89 } | |
90 }; | |
91 | |
92 /** | |
93 * Implementation of an interface that notifies BluetoothProfile IPC clients w hen they have been | |
94 * connected to or disconnected from the service. | |
95 */ | |
96 private class BluetoothServiceListener implements BluetoothProfile.ServiceList ener { | |
97 @Override | |
98 // Called to notify the client when the proxy object has been connected to t he service. | |
99 // Once we have the profile proxy object, we can use it to monitor the state of the | |
100 // connection and perform other operations that are relevant to the headset profile. | |
101 public void onServiceConnected(int profile, BluetoothProfile proxy) { | |
102 if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITI ALIZED) { | |
103 return; | |
104 } | |
105 Log.d(TAG, "BluetoothServiceListener.onServiceConnected: BT state=" + blue toothState); | |
106 // Android only supports one connected Bluetooth Headset at a time. | |
107 bluetoothHeadset = (BluetoothHeadset) proxy; | |
108 updateAudioDeviceState(); | |
109 Log.d(TAG, "onServiceConnected done: BT state=" + bluetoothState); | |
110 } | |
111 | |
112 @Override | |
113 /** Notifies the client when the proxy object has been disconnected from the service. */ | |
114 public void onServiceDisconnected(int profile) { | |
115 if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITI ALIZED) { | |
116 return; | |
117 } | |
118 Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT state=" + b luetoothState); | |
119 stopScoAudio(); | |
120 bluetoothHeadset = null; | |
121 bluetoothDevice = null; | |
122 bluetoothState = State.HEADSET_UNAVAILABLE; | |
123 updateAudioDeviceState(); | |
124 Log.d(TAG, "onServiceDisconnected done: BT state=" + bluetoothState); | |
125 } | |
126 } | |
127 | |
128 // Intent broadcast receiver which handles changes in Bluetooth device availab ility. | |
129 // Detects headset changes and Bluetooth SCO state changes. | |
130 private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver { | |
131 @Override | |
132 public void onReceive(Context context, Intent intent) { | |
133 if (bluetoothState == State.UNINITIALIZED) { | |
134 return; | |
135 } | |
136 final String action = intent.getAction(); | |
137 // Change in connection state of the Headset profile. Note that the | |
138 // change does not tell us anything about whether we're streaming | |
139 // audio to BT over SCO. Typically received when user turns on a BT | |
140 // headset while audio is active using another audio device. | |
141 if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { | |
142 final int state = | |
143 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.ST ATE_DISCONNECTED); | |
144 Log.d(TAG, "BluetoothHeadsetBroadcastReceiver.onReceive: " | |
145 + "a=ACTION_CONNECTION_STATE_CHANGED, " | |
146 + "s=" + stateToString(state) + ", " | |
147 + "sb=" + isInitialStickyBroadcast() + ", " | |
148 + "BT state: " + bluetoothState); | |
149 if (state == BluetoothHeadset.STATE_CONNECTED) { | |
150 scoConnectionAttempts = 0; | |
151 updateAudioDeviceState(); | |
152 } else if (state == BluetoothHeadset.STATE_CONNECTING) { | |
153 // No action needed. | |
154 } else if (state == BluetoothHeadset.STATE_DISCONNECTING) { | |
155 // No action needed. | |
156 } else if (state == BluetoothHeadset.STATE_DISCONNECTED) { | |
157 // Bluetooth is probably powered off during the call. | |
158 stopScoAudio(); | |
159 updateAudioDeviceState(); | |
160 } | |
161 // Change in the audio (SCO) connection state of the Headset profile. | |
162 // Typically received after call to startScoAudio() has finalized. | |
163 } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { | |
164 final int state = intent.getIntExtra( | |
165 BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNEC TED); | |
166 Log.d(TAG, "BluetoothHeadsetBroadcastReceiver.onReceive: " | |
167 + "a=ACTION_AUDIO_STATE_CHANGED, " | |
168 + "s=" + stateToString(state) + ", " | |
169 + "sb=" + isInitialStickyBroadcast() + ", " | |
170 + "BT state: " + bluetoothState); | |
171 if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { | |
172 cancelTimer(); | |
173 if (bluetoothState == State.SCO_CONNECTING) { | |
174 Log.d(TAG, "+++ Bluetooth audio SCO is now connected"); | |
175 bluetoothState = State.SCO_CONNECTED; | |
176 scoConnectionAttempts = 0; | |
177 updateAudioDeviceState(); | |
178 } else { | |
179 Log.w(TAG, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED" ); | |
180 } | |
181 } else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) { | |
182 Log.d(TAG, "+++ Bluetooth audio SCO is now connecting..."); | |
183 } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { | |
184 Log.d(TAG, "+++ Bluetooth audio SCO is now disconnected"); | |
185 if (isInitialStickyBroadcast()) { | |
186 Log.d(TAG, "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadcast ."); | |
187 return; | |
188 } | |
189 updateAudioDeviceState(); | |
190 } | |
191 } | |
192 Log.d(TAG, "onReceive done: BT state=" + bluetoothState); | |
193 } | |
194 }; | |
195 | |
196 /** Construction. */ | |
197 static AppRTCBluetoothManager create(Context context, AppRTCAudioManager audio Manager) { | |
198 Log.d(TAG, "create" + AppRTCUtils.getThreadInfo()); | |
199 return new AppRTCBluetoothManager(context, audioManager); | |
200 } | |
201 | |
202 protected AppRTCBluetoothManager(Context context, AppRTCAudioManager audioMana ger) { | |
203 Log.d(TAG, "ctor"); | |
204 ThreadUtils.checkIsOnMainThread(); | |
magjed_webrtc
2016/12/10 19:34:45
Use threadChecker.checkIsOnValidThread() instead.
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
205 apprtcContext = context; | |
206 apprtcAudioManager = audioManager; | |
207 this.audioManager = getAudioManager(context); | |
208 bluetoothState = State.UNINITIALIZED; | |
209 bluetoothServiceListener = new BluetoothServiceListener(); | |
210 bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver(); | |
211 handler = new Handler(Looper.getMainLooper()); | |
212 } | |
213 | |
214 /** Returns the internal state. */ | |
215 public State getState() { | |
216 threadChecker.checkIsOnValidThread(); | |
217 return bluetoothState; | |
218 } | |
219 | |
220 /** | |
221 * Activates components required to detect Bluetooth devices and to enable | |
222 * BT SCO (audio is routed via BT SCO) for the headset profile. The end | |
223 * state will be HEADSET_UNAVAILABLE but a state machine has started which | |
224 * will start a state change sequence where the final outcome depends on | |
225 * if/when the BT headset is enabled. | |
226 * Example of state change sequence when start() is called while BT device | |
227 * is connected and enabled: | |
228 * UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE --> | |
229 * SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO. | |
230 * Note that the AppRTCAudioManager is also involved in driving this state | |
231 * change. | |
232 */ | |
233 public void start() { | |
234 threadChecker.checkIsOnValidThread(); | |
235 Log.d(TAG, "start"); | |
236 if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) { | |
237 Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permissi on"); | |
238 return; | |
239 } | |
240 if (bluetoothState != State.UNINITIALIZED) { | |
241 Log.w(TAG, "Invalid BT state"); | |
242 return; | |
243 } | |
244 bluetoothHeadset = null; | |
245 bluetoothDevice = null; | |
246 scoConnectionAttempts = 0; | |
247 // Get a handle to the default local Bluetooth adapter. | |
248 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | |
249 if (bluetoothAdapter == null) { | |
250 Log.w(TAG, "Device does not support Bluetooth"); | |
251 return; | |
252 } | |
253 // Ensure that the device supports use of BT SCO audio for off call use case s. | |
254 if (!audioManager.isBluetoothScoAvailableOffCall()) { | |
255 Log.e(TAG, "Bluetooth SCO audio is not available off call"); | |
256 return; | |
257 } | |
258 logBluetoothAdapterInfo(bluetoothAdapter); | |
259 // Establish a connection to the HEADSET profile (includes both Bluetooth He adset and | |
260 // Hands-Free) proxy object and install a listener. | |
261 if (!getBluetoothProfileProxy( | |
262 apprtcContext, bluetoothServiceListener, BluetoothProfile.HEADSET)) { | |
263 Log.e(TAG, "BluetoothAdapter.getProfileProxy(HEADSET) failed"); | |
264 return; | |
265 } | |
266 // Register receivers for BluetoothHeadset change notifications. | |
267 IntentFilter bluetoothHeadsetFilter = new IntentFilter(); | |
268 // Register receiver for change in connection state of the Headset profile. | |
269 bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CH ANGED); | |
270 // Register receiver for change in audio connection state of the Headset pro file. | |
271 bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED ); | |
272 registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter); | |
273 Log.d(TAG, "HEADSET profile state: " | |
274 + stateToString(bluetoothAdapter.getProfileConnectionState(Bluetooth Profile.HEADSET))); | |
275 Log.d(TAG, "Bluetooth proxy for headset profile has started"); | |
276 bluetoothState = State.HEADSET_UNAVAILABLE; | |
277 Log.d(TAG, "start done: BT state=" + bluetoothState); | |
278 } | |
279 | |
280 /** Stops and closes all components related to Bluetooth audio. */ | |
281 public void stop() { | |
282 threadChecker.checkIsOnValidThread(); | |
283 unregisterReceiver(bluetoothHeadsetReceiver); | |
284 Log.d(TAG, "stop: BT state=" + bluetoothState); | |
285 if (bluetoothAdapter != null) { | |
286 // Stop BT SCO connection with remote device if needed. | |
287 stopScoAudio(); | |
288 // Close down remaining BT resources. | |
289 if (bluetoothState != State.UNINITIALIZED) { | |
290 cancelTimer(); | |
291 if (bluetoothHeadset != null) { | |
292 bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetooth Headset); | |
293 bluetoothHeadset = null; | |
294 } | |
295 bluetoothAdapter = null; | |
296 bluetoothDevice = null; | |
297 bluetoothState = State.UNINITIALIZED; | |
298 } | |
299 } | |
300 Log.d(TAG, "stop done: BT state=" + bluetoothState); | |
301 } | |
302 | |
303 /** | |
304 * Starts Bluetooth SCO connection with remote device. | |
305 * Note that the phone application always has the priority on the usage of the SCO connection | |
306 * for telephony. If this method is called while the phone is in call it will be ignored. | |
307 * Similarly, if a call is received or sent while an application is using the SCO connection, | |
308 * the connection will be lost for the application and NOT returned automatica lly when the call | |
309 * ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a | |
310 * virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_M R2 only a raw SCO | |
311 * audio connection is established. | |
312 * TODO(henrika): should we add support for virtual voice call to BT headset a lso for JBMR2 and | |
313 * higher. It might be required to initiates a virtual voice call since many d evices do not | |
314 * accept SCO audio without a "call". | |
315 */ | |
316 public boolean startScoAudio() { | |
317 threadChecker.checkIsOnValidThread(); | |
318 Log.d(TAG, "startSco: BT state=" + bluetoothState + ", " | |
319 + "attempts: " + scoConnectionAttempts + ", " | |
320 + "SCO is on: " + isScoOn()); | |
321 if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) { | |
322 Log.e(TAG, "BT SCO connection fails - no more attempts"); | |
323 return false; | |
324 } | |
325 if (bluetoothState != State.HEADSET_AVAILABLE) { | |
326 Log.e(TAG, "BT SCO connection fails - no headset available"); | |
327 return false; | |
328 } | |
329 // Start BT SCO channel and wait for ACTION_AUDIO_STATE_CHANGED. | |
330 Log.d(TAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED. .."); | |
331 // The SCO connection establishment can take several seconds, hence we canno t rely on the | |
332 // connection to be available when the method returns but instead register t o receive the | |
333 // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AU DIO_STATE_CONNECTED. | |
334 bluetoothState = State.SCO_CONNECTING; | |
335 audioManager.startBluetoothSco(); | |
336 scoConnectionAttempts++; | |
337 startTimer(); | |
338 Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState); | |
339 return true; | |
340 } | |
341 | |
342 /** Stops Bluetooth SCO connection with remote device. */ | |
343 public void stopScoAudio() { | |
344 threadChecker.checkIsOnValidThread(); | |
345 Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", " | |
346 + "SCO is on: " + isScoOn()); | |
347 if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CO NNECTED) { | |
348 return; | |
349 } | |
350 cancelTimer(); | |
351 audioManager.stopBluetoothSco(); | |
352 bluetoothState = State.SCO_DISCONNECTING; | |
353 Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState); | |
354 } | |
355 | |
356 /** | |
357 * Use the BluetoothHeadset proxy object (controls the Bluetooth Headset | |
358 * Service via IPC) to update the list of connected devices for the HEADSET | |
359 * profile. The internal state will change to HEADSET_UNAVAILABLE or to | |
360 * HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected | |
361 * device if available. | |
362 */ | |
363 public void updateDevice() { | |
364 if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) { | |
365 return; | |
366 } | |
367 Log.d(TAG, "updateDevice"); | |
368 // Get connected devices for the headset profile. Returns the set of | |
369 // devices which are in state STATE_CONNECTED. The BluetoothDevice class | |
370 // is just a thin wrapper for a Bluetooth hardware address. | |
371 List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices(); | |
372 if (devices.size() == 0) { | |
magjed_webrtc
2016/12/10 19:34:44
nit: Use devices.isEmpty() instead.
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
373 bluetoothDevice = null; | |
374 } else { | |
375 // Always use first device is list. Android only supports one device. | |
376 bluetoothDevice = devices.get(0); | |
377 } | |
378 if (bluetoothDevice == null) { | |
magjed_webrtc
2016/12/10 19:34:44
Join this if-statement with the one above since th
henrika_webrtc
2016/12/12 14:13:25
Thanks. Done!
| |
379 bluetoothState = State.HEADSET_UNAVAILABLE; | |
380 Log.d(TAG, "No connected bluetooth headset"); | |
381 } else { | |
382 bluetoothState = State.HEADSET_AVAILABLE; | |
383 Log.d(TAG, "Connected bluetooth headset: " | |
384 + "name=" + bluetoothDevice.getName() + ", " | |
385 + "state=" + stateToString(bluetoothHeadset.getConnectionState(blu etoothDevice)) | |
386 + ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevi ce)); | |
387 } | |
388 Log.d(TAG, "updateDevice done: BT state=" + bluetoothState); | |
389 } | |
390 | |
391 /** | |
392 * Stubs for test mocks. | |
393 */ | |
394 protected AudioManager getAudioManager(Context context) { | |
395 return (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); | |
396 } | |
397 | |
398 protected void registerReceiver(BroadcastReceiver receiver, IntentFilter filte r) { | |
399 apprtcContext.registerReceiver(receiver, filter); | |
400 } | |
401 | |
402 protected void unregisterReceiver(BroadcastReceiver receiver) { | |
403 apprtcContext.unregisterReceiver(receiver); | |
404 } | |
405 | |
406 protected boolean getBluetoothProfileProxy( | |
407 Context context, BluetoothProfile.ServiceListener listener, int profile) { | |
408 return bluetoothAdapter.getProfileProxy(context, listener, profile); | |
409 } | |
410 | |
411 protected boolean hasPermission(Context context, String permission) { | |
412 return apprtcContext.checkPermission(permission, Process.myPid(), Process.my Uid()) | |
413 == PackageManager.PERMISSION_GRANTED; | |
414 } | |
415 | |
416 /** Logs the state of the local Bluetooth adapter. */ | |
417 protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) { | |
418 Log.d(TAG, "BluetoothAdapter: " | |
419 + "enabled=" + localAdapter.isEnabled() + ", " | |
420 + "state=" + stateToString(localAdapter.getState()) + ", " | |
421 + "name=" + localAdapter.getName() + ", " | |
422 + "address=" + localAdapter.getAddress()); | |
423 // Log the set of BluetoothDevice objects that are bonded (paired) to the lo cal adapter. | |
424 Set<BluetoothDevice> pairedDevices = localAdapter.getBondedDevices(); | |
425 if (pairedDevices.size() > 0) { | |
magjed_webrtc
2016/12/10 19:34:45
nit: !pairedDevices.isEmpty()
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
426 Log.d(TAG, "paired devices:"); | |
427 for (BluetoothDevice device : pairedDevices) { | |
428 Log.d(TAG, " name=" + device.getName() + ", address=" + device.getAddres s()); | |
429 } | |
430 } | |
431 } | |
432 | |
433 /** Ensures that the audio manager updates its list of available audio devices . */ | |
434 private void updateAudioDeviceState() { | |
435 threadChecker.checkIsOnValidThread(); | |
436 Log.d(TAG, "updateAudioDeviceState"); | |
437 apprtcAudioManager.updateAudioDeviceState(); | |
438 } | |
439 | |
440 /** Starts timer which times out after BLUETOOTH_SCO_TIMEOUT_MS milliseconds. */ | |
441 private void startTimer() { | |
442 threadChecker.checkIsOnValidThread(); | |
443 Log.d(TAG, "startTimer"); | |
444 handler.postDelayed(bluetoothTimeoutRunnable, BLUETOOTH_SCO_TIMEOUT_MS); | |
445 } | |
446 | |
447 /** Cancels any outstanding timer tasks. */ | |
448 private void cancelTimer() { | |
449 threadChecker.checkIsOnValidThread(); | |
450 Log.d(TAG, "cancelTimer"); | |
451 handler.removeCallbacks(bluetoothTimeoutRunnable); | |
452 } | |
453 | |
454 /** | |
455 * Called when start of the BT SCO channel takes too long time. Usually | |
456 * happens when the BT device has been turned on during an ongoing call. | |
457 */ | |
458 private void bluetoothTimeout() { | |
459 threadChecker.checkIsOnValidThread(); | |
460 if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) { | |
461 return; | |
462 } | |
463 Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", " | |
464 + "attempts: " + scoConnectionAttempts + ", " | |
465 + "SCO is on: " + isScoOn()); | |
466 if (bluetoothState != State.SCO_CONNECTING) { | |
467 return; | |
468 } | |
469 // Bluetooth SCO should be connecting; check the latest result. | |
470 boolean scoConnected = false; | |
471 List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices(); | |
472 if (devices.size() > 0) { | |
473 bluetoothDevice = devices.get(0); | |
474 if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) { | |
475 Log.d(TAG, "SCO connected with " + bluetoothDevice.getName()); | |
476 scoConnected = true; | |
477 } else { | |
478 Log.d(TAG, "SCO is not connected with " + bluetoothDevice.getName()); | |
479 } | |
480 } | |
481 if (scoConnected) { | |
482 // We thought BT had timed out, but it's actually on; updating state. | |
483 bluetoothState = State.SCO_CONNECTED; | |
484 scoConnectionAttempts = 0; | |
485 updateAudioDeviceState(); | |
magjed_webrtc
2016/12/10 19:34:44
Move this after the if-statement since you do it l
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
486 } else { | |
487 // Give up and "cancel" our request by calling stopBluetoothSco(). | |
488 Log.w(TAG, "BT failed to connect after timeout"); | |
489 stopScoAudio(); | |
490 updateAudioDeviceState(); | |
491 } | |
492 Log.d(TAG, "bluetoothTimeout done: BT state=" + bluetoothState); | |
493 } | |
494 | |
495 /** Checks whether audio uses Bluetooth SCO. */ | |
496 private boolean isScoOn() { | |
497 return audioManager.isBluetoothScoOn(); | |
magjed_webrtc
2016/12/10 19:34:44
I personally would just inline audioManager.isBlue
henrika_webrtc
2016/12/12 14:13:25
ACK, used same style in AudioManager. Keeping as i
| |
498 } | |
499 | |
500 /** Converts BluetoothAdapter states into local string representations. */ | |
501 private String stateToString(int state) { | |
502 String stateString; | |
503 switch (state) { | |
504 case BluetoothAdapter.STATE_DISCONNECTED: | |
505 // The profile is in disconnected state. | |
magjed_webrtc
2016/12/10 19:34:45
Remove |stateString| and just do
return "DISCONNEC
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
506 stateString = "DISCONNECTED"; | |
507 break; | |
508 case BluetoothAdapter.STATE_CONNECTED: | |
509 // The profile is in connected state. | |
magjed_webrtc
2016/12/10 19:34:45
Many of these comments provide no extra informatio
henrika_webrtc
2016/12/12 14:13:25
Agree. Removed.
| |
510 stateString = "CONNECTED"; | |
511 break; | |
512 case BluetoothAdapter.STATE_CONNECTING: | |
513 // The profile is in connecting state. | |
514 stateString = "CONNECTING"; | |
515 break; | |
516 case BluetoothAdapter.STATE_DISCONNECTING: | |
517 // The profile is in disconnecting state. | |
518 stateString = "DISCONNECTING"; | |
519 break; | |
520 case BluetoothAdapter.STATE_OFF: | |
521 // Indicates the local Bluetooth adapter is off. | |
522 stateString = "OFF"; | |
523 break; | |
524 case BluetoothAdapter.STATE_ON: | |
525 // Indicates the local Bluetooth adapter is on, and ready for use. | |
526 stateString = "ON"; | |
527 break; | |
528 case BluetoothAdapter.STATE_TURNING_OFF: | |
529 // Indicates the local Bluetooth adapter is turning off. Local clients s hould immediately | |
530 // attempt graceful disconnection of any remote links. | |
531 stateString = "TURNING_OFF"; | |
532 break; | |
533 case BluetoothAdapter.STATE_TURNING_ON: | |
534 // Indicates the local Bluetooth adapter is turning on. However local cl ients should wait | |
535 // for STATE_ON before attempting to use the adapter. | |
536 stateString = "TURNING_ON"; | |
537 break; | |
538 default: | |
539 stateString = "INVALID"; | |
540 break; | |
541 } | |
542 return stateString; | |
543 } | |
544 } | |
OLD | NEW |