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