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