Chromium Code Reviews| 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 |