1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * Bluetooth A2DP StateMachine. There is one instance per remote device. 19 * - "Disconnected" and "Connected" are steady states. 20 * - "Connecting" and "Disconnecting" are transient states until the 21 * connection / disconnection is completed. 22 * 23 * 24 * (Disconnected) 25 * | ^ 26 * CONNECT | | DISCONNECTED 27 * V | 28 * (Connecting)<--->(Disconnecting) 29 * | ^ 30 * CONNECTED | | DISCONNECT 31 * V | 32 * (Connected) 33 * NOTES: 34 * - If state machine is in "Connecting" state and the remote device sends 35 * DISCONNECT request, the state machine transitions to "Disconnecting" state. 36 * - Similarly, if the state machine is in "Disconnecting" state and the remote device 37 * sends CONNECT request, the state machine transitions to "Connecting" state. 38 * 39 * DISCONNECT 40 * (Connecting) ---------------> (Disconnecting) 41 * <--------------- 42 * CONNECT 43 * 44 */ 45 46 package com.android.bluetooth.a2dp; 47 48 import android.bluetooth.BluetoothA2dp; 49 import android.bluetooth.BluetoothCodecConfig; 50 import android.bluetooth.BluetoothCodecStatus; 51 import android.bluetooth.BluetoothDevice; 52 import android.bluetooth.BluetoothProfile; 53 import android.content.Intent; 54 import android.os.Looper; 55 import android.os.Message; 56 import android.support.annotation.VisibleForTesting; 57 import android.util.Log; 58 59 import com.android.bluetooth.btservice.ProfileService; 60 import com.android.internal.util.State; 61 import com.android.internal.util.StateMachine; 62 63 import java.io.FileDescriptor; 64 import java.io.PrintWriter; 65 import java.io.StringWriter; 66 import java.util.Scanner; 67 68 final class A2dpStateMachine extends StateMachine { 69 private static final boolean DBG = true; 70 private static final String TAG = "A2dpStateMachine"; 71 72 static final int CONNECT = 1; 73 static final int DISCONNECT = 2; 74 @VisibleForTesting 75 static final int STACK_EVENT = 101; 76 private static final int CONNECT_TIMEOUT = 201; 77 78 // NOTE: the value is not "final" - it is modified in the unit tests 79 @VisibleForTesting 80 static int sConnectTimeoutMs = 30000; // 30s 81 82 private Disconnected mDisconnected; 83 private Connecting mConnecting; 84 private Disconnecting mDisconnecting; 85 private Connected mConnected; 86 private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED; 87 private int mLastConnectionState = -1; 88 89 private A2dpService mA2dpService; 90 private A2dpNativeInterface mA2dpNativeInterface; 91 private boolean mA2dpOffloadEnabled = false; 92 private final BluetoothDevice mDevice; 93 private boolean mIsPlaying = false; 94 private BluetoothCodecStatus mCodecStatus; 95 96 A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService, 97 A2dpNativeInterface a2dpNativeInterface, Looper looper) { 98 super(TAG, looper); 99 setDbg(DBG); 100 mDevice = device; 101 mA2dpService = a2dpService; 102 mA2dpNativeInterface = a2dpNativeInterface; 103 104 mDisconnected = new Disconnected(); 105 mConnecting = new Connecting(); 106 mDisconnecting = new Disconnecting(); 107 mConnected = new Connected(); 108 109 addState(mDisconnected); 110 addState(mConnecting); 111 addState(mDisconnecting); 112 addState(mConnected); 113 mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled; 114 115 setInitialState(mDisconnected); 116 } 117 118 static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService, 119 A2dpNativeInterface a2dpNativeInterface, Looper looper) { 120 Log.i(TAG, "make for device " + device); 121 A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface, 122 looper); 123 a2dpSm.start(); 124 return a2dpSm; 125 } 126 127 public void doQuit() { 128 log("doQuit for device " + mDevice); 129 if (mIsPlaying) { 130 // Stop if auido is still playing 131 log("doQuit: stopped playing " + mDevice); 132 mIsPlaying = false; 133 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING); 134 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 135 BluetoothA2dp.STATE_PLAYING); 136 } 137 quitNow(); 138 } 139 140 public void cleanup() { 141 log("cleanup for device " + mDevice); 142 } 143 144 @VisibleForTesting 145 class Disconnected extends State { 146 @Override 147 public void enter() { 148 Message currentMessage = getCurrentMessage(); 149 Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" 150 : messageWhatToString(currentMessage.what))); 151 mConnectionState = BluetoothProfile.STATE_DISCONNECTED; 152 153 removeDeferredMessages(DISCONNECT); 154 155 if (mLastConnectionState != -1) { 156 // Don't broadcast during startup 157 broadcastConnectionState(mConnectionState, mLastConnectionState); 158 if (mIsPlaying) { 159 Log.i(TAG, "Disconnected: stopped playing: " + mDevice); 160 mIsPlaying = false; 161 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING); 162 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 163 BluetoothA2dp.STATE_PLAYING); 164 } 165 } 166 } 167 168 @Override 169 public void exit() { 170 Message currentMessage = getCurrentMessage(); 171 log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" 172 : messageWhatToString(currentMessage.what))); 173 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; 174 } 175 176 @Override 177 public boolean processMessage(Message message) { 178 log("Disconnected process message(" + mDevice + "): " 179 + messageWhatToString(message.what)); 180 181 switch (message.what) { 182 case CONNECT: 183 Log.i(TAG, "Connecting to " + mDevice); 184 if (!mA2dpNativeInterface.connectA2dp(mDevice)) { 185 Log.e(TAG, "Disconnected: error connecting to " + mDevice); 186 break; 187 } 188 if (mA2dpService.okToConnect(mDevice, true)) { 189 transitionTo(mConnecting); 190 } else { 191 // Reject the request and stay in Disconnected state 192 Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice); 193 } 194 break; 195 case DISCONNECT: 196 Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); 197 break; 198 case STACK_EVENT: 199 A2dpStackEvent event = (A2dpStackEvent) message.obj; 200 log("Disconnected: stack event: " + event); 201 if (!mDevice.equals(event.device)) { 202 Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); 203 } 204 switch (event.type) { 205 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 206 processConnectionEvent(event.valueInt); 207 break; 208 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 209 processCodecConfigEvent(event.codecStatus); 210 break; 211 default: 212 Log.e(TAG, "Disconnected: ignoring stack event: " + event); 213 break; 214 } 215 break; 216 default: 217 return NOT_HANDLED; 218 } 219 return HANDLED; 220 } 221 222 // in Disconnected state 223 private void processConnectionEvent(int event) { 224 switch (event) { 225 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 226 Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice); 227 break; 228 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 229 if (mA2dpService.okToConnect(mDevice, false)) { 230 Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice); 231 transitionTo(mConnecting); 232 } else { 233 // Reject the connection and stay in Disconnected state itself 234 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); 235 mA2dpNativeInterface.disconnectA2dp(mDevice); 236 } 237 break; 238 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 239 Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice); 240 if (mA2dpService.okToConnect(mDevice, false)) { 241 Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice); 242 transitionTo(mConnected); 243 } else { 244 // Reject the connection and stay in Disconnected state itself 245 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice); 246 mA2dpNativeInterface.disconnectA2dp(mDevice); 247 } 248 break; 249 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 250 Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice); 251 break; 252 default: 253 Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice); 254 break; 255 } 256 } 257 } 258 259 @VisibleForTesting 260 class Connecting extends State { 261 @Override 262 public void enter() { 263 Message currentMessage = getCurrentMessage(); 264 Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null" 265 : messageWhatToString(currentMessage.what))); 266 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); 267 mConnectionState = BluetoothProfile.STATE_CONNECTING; 268 broadcastConnectionState(mConnectionState, mLastConnectionState); 269 } 270 271 @Override 272 public void exit() { 273 Message currentMessage = getCurrentMessage(); 274 log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null" 275 : messageWhatToString(currentMessage.what))); 276 mLastConnectionState = BluetoothProfile.STATE_CONNECTING; 277 removeMessages(CONNECT_TIMEOUT); 278 } 279 280 @Override 281 public boolean processMessage(Message message) { 282 log("Connecting process message(" + mDevice + "): " 283 + messageWhatToString(message.what)); 284 285 switch (message.what) { 286 case CONNECT: 287 deferMessage(message); 288 break; 289 case CONNECT_TIMEOUT: { 290 Log.w(TAG, "Connecting connection timeout: " + mDevice); 291 mA2dpNativeInterface.disconnectA2dp(mDevice); 292 A2dpStackEvent event = 293 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 294 event.device = mDevice; 295 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; 296 sendMessage(STACK_EVENT, event); 297 break; 298 } 299 case DISCONNECT: 300 // Cancel connection 301 Log.i(TAG, "Connecting: connection canceled to " + mDevice); 302 mA2dpNativeInterface.disconnectA2dp(mDevice); 303 transitionTo(mDisconnected); 304 break; 305 case STACK_EVENT: 306 A2dpStackEvent event = (A2dpStackEvent) message.obj; 307 log("Connecting: stack event: " + event); 308 if (!mDevice.equals(event.device)) { 309 Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); 310 } 311 switch (event.type) { 312 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 313 processConnectionEvent(event.valueInt); 314 break; 315 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 316 processCodecConfigEvent(event.codecStatus); 317 break; 318 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 319 break; 320 default: 321 Log.e(TAG, "Connecting: ignoring stack event: " + event); 322 break; 323 } 324 break; 325 default: 326 return NOT_HANDLED; 327 } 328 return HANDLED; 329 } 330 331 // in Connecting state 332 private void processConnectionEvent(int event) { 333 switch (event) { 334 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 335 Log.w(TAG, "Connecting device disconnected: " + mDevice); 336 transitionTo(mDisconnected); 337 break; 338 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 339 transitionTo(mConnected); 340 break; 341 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 342 // Ignored - probably an event that the outgoing connection was initiated 343 break; 344 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 345 Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); 346 transitionTo(mDisconnecting); 347 break; 348 default: 349 Log.e(TAG, "Incorrect event: " + event); 350 break; 351 } 352 } 353 } 354 355 @VisibleForTesting 356 class Disconnecting extends State { 357 @Override 358 public void enter() { 359 Message currentMessage = getCurrentMessage(); 360 Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" 361 : messageWhatToString(currentMessage.what))); 362 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); 363 mConnectionState = BluetoothProfile.STATE_DISCONNECTING; 364 broadcastConnectionState(mConnectionState, mLastConnectionState); 365 } 366 367 @Override 368 public void exit() { 369 Message currentMessage = getCurrentMessage(); 370 log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" 371 : messageWhatToString(currentMessage.what))); 372 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; 373 removeMessages(CONNECT_TIMEOUT); 374 } 375 376 @Override 377 public boolean processMessage(Message message) { 378 log("Disconnecting process message(" + mDevice + "): " 379 + messageWhatToString(message.what)); 380 381 switch (message.what) { 382 case CONNECT: 383 deferMessage(message); 384 break; 385 case CONNECT_TIMEOUT: { 386 Log.w(TAG, "Disconnecting connection timeout: " + mDevice); 387 mA2dpNativeInterface.disconnectA2dp(mDevice); 388 A2dpStackEvent event = 389 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 390 event.device = mDevice; 391 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; 392 sendMessage(STACK_EVENT, event); 393 break; 394 } 395 case DISCONNECT: 396 deferMessage(message); 397 break; 398 case STACK_EVENT: 399 A2dpStackEvent event = (A2dpStackEvent) message.obj; 400 log("Disconnecting: stack event: " + event); 401 if (!mDevice.equals(event.device)) { 402 Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); 403 } 404 switch (event.type) { 405 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 406 processConnectionEvent(event.valueInt); 407 break; 408 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 409 processCodecConfigEvent(event.codecStatus); 410 break; 411 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 412 default: 413 Log.e(TAG, "Disconnecting: ignoring stack event: " + event); 414 break; 415 } 416 break; 417 default: 418 return NOT_HANDLED; 419 } 420 return HANDLED; 421 } 422 423 // in Disconnecting state 424 private void processConnectionEvent(int event) { 425 switch (event) { 426 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 427 Log.i(TAG, "Disconnected: " + mDevice); 428 transitionTo(mDisconnected); 429 break; 430 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 431 if (mA2dpService.okToConnect(mDevice, false)) { 432 Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); 433 transitionTo(mConnected); 434 } else { 435 // Reject the connection and stay in Disconnecting state 436 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice); 437 mA2dpNativeInterface.disconnectA2dp(mDevice); 438 } 439 break; 440 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 441 if (mA2dpService.okToConnect(mDevice, false)) { 442 Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); 443 transitionTo(mConnecting); 444 } else { 445 // Reject the connection and stay in Disconnecting state 446 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); 447 mA2dpNativeInterface.disconnectA2dp(mDevice); 448 } 449 break; 450 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 451 // We are already disconnecting, do nothing 452 break; 453 default: 454 Log.e(TAG, "Incorrect event: " + event); 455 break; 456 } 457 } 458 } 459 460 @VisibleForTesting 461 class Connected extends State { 462 @Override 463 public void enter() { 464 Message currentMessage = getCurrentMessage(); 465 Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null" 466 : messageWhatToString(currentMessage.what))); 467 mConnectionState = BluetoothProfile.STATE_CONNECTED; 468 469 removeDeferredMessages(CONNECT); 470 471 broadcastConnectionState(mConnectionState, mLastConnectionState); 472 // Upon connected, the audio starts out as stopped 473 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 474 BluetoothA2dp.STATE_PLAYING); 475 } 476 477 @Override 478 public void exit() { 479 Message currentMessage = getCurrentMessage(); 480 log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null" 481 : messageWhatToString(currentMessage.what))); 482 mLastConnectionState = BluetoothProfile.STATE_CONNECTED; 483 } 484 485 @Override 486 public boolean processMessage(Message message) { 487 log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what)); 488 489 switch (message.what) { 490 case CONNECT: 491 Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); 492 break; 493 case DISCONNECT: { 494 Log.i(TAG, "Disconnecting from " + mDevice); 495 if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) { 496 // If error in the native stack, transition directly to Disconnected state. 497 Log.e(TAG, "Connected: error disconnecting from " + mDevice); 498 transitionTo(mDisconnected); 499 break; 500 } 501 transitionTo(mDisconnecting); 502 } 503 break; 504 case STACK_EVENT: 505 A2dpStackEvent event = (A2dpStackEvent) message.obj; 506 log("Connected: stack event: " + event); 507 if (!mDevice.equals(event.device)) { 508 Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); 509 } 510 switch (event.type) { 511 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 512 processConnectionEvent(event.valueInt); 513 break; 514 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 515 processAudioStateEvent(event.valueInt); 516 break; 517 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 518 processCodecConfigEvent(event.codecStatus); 519 break; 520 default: 521 Log.e(TAG, "Connected: ignoring stack event: " + event); 522 break; 523 } 524 break; 525 default: 526 return NOT_HANDLED; 527 } 528 return HANDLED; 529 } 530 531 // in Connected state 532 private void processConnectionEvent(int event) { 533 switch (event) { 534 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 535 Log.i(TAG, "Disconnected from " + mDevice); 536 transitionTo(mDisconnected); 537 break; 538 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 539 Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice); 540 break; 541 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 542 Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice); 543 break; 544 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 545 Log.i(TAG, "Disconnecting from " + mDevice); 546 transitionTo(mDisconnecting); 547 break; 548 default: 549 Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); 550 break; 551 } 552 } 553 554 // in Connected state 555 private void processAudioStateEvent(int state) { 556 switch (state) { 557 case A2dpStackEvent.AUDIO_STATE_STARTED: 558 synchronized (this) { 559 if (!mIsPlaying) { 560 Log.i(TAG, "Connected: started playing: " + mDevice); 561 mIsPlaying = true; 562 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING); 563 broadcastAudioState(BluetoothA2dp.STATE_PLAYING, 564 BluetoothA2dp.STATE_NOT_PLAYING); 565 } 566 } 567 break; 568 case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND: 569 case A2dpStackEvent.AUDIO_STATE_STOPPED: 570 synchronized (this) { 571 if (mIsPlaying) { 572 Log.i(TAG, "Connected: stopped playing: " + mDevice); 573 mIsPlaying = false; 574 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING); 575 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 576 BluetoothA2dp.STATE_PLAYING); 577 } 578 } 579 break; 580 default: 581 Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); 582 break; 583 } 584 } 585 } 586 587 int getConnectionState() { 588 return mConnectionState; 589 } 590 591 BluetoothDevice getDevice() { 592 return mDevice; 593 } 594 595 boolean isConnected() { 596 synchronized (this) { 597 return (getCurrentState() == mConnected); 598 } 599 } 600 601 boolean isPlaying() { 602 synchronized (this) { 603 return mIsPlaying; 604 } 605 } 606 607 BluetoothCodecStatus getCodecStatus() { 608 synchronized (this) { 609 return mCodecStatus; 610 } 611 } 612 613 // NOTE: This event is processed in any state 614 private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) { 615 BluetoothCodecConfig prevCodecConfig = null; 616 synchronized (this) { 617 if (mCodecStatus != null) { 618 prevCodecConfig = mCodecStatus.getCodecConfig(); 619 } 620 mCodecStatus = newCodecStatus; 621 } 622 623 if (DBG) { 624 Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->" 625 + newCodecStatus.getCodecConfig()); 626 for (BluetoothCodecConfig codecConfig : 627 newCodecStatus.getCodecsLocalCapabilities()) { 628 Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig); 629 } 630 for (BluetoothCodecConfig codecConfig : 631 newCodecStatus.getCodecsSelectableCapabilities()) { 632 Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig); 633 } 634 } 635 636 if (mA2dpOffloadEnabled) { 637 boolean update = false; 638 BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig(); 639 if ((prevCodecConfig != null) 640 && (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) { 641 update = true; 642 } else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) { 643 update = true; 644 } else if ((newCodecConfig.getCodecType() 645 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) 646 && (prevCodecConfig != null) 647 && (prevCodecConfig.getCodecSpecific1() 648 != newCodecConfig.getCodecSpecific1())) { 649 update = true; 650 } 651 if (update) { 652 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false); 653 } 654 return; 655 } 656 657 boolean sameAudioFeedingParameters = 658 newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig); 659 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters); 660 } 661 662 // This method does not check for error conditon (newState == prevState) 663 private void broadcastConnectionState(int newState, int prevState) { 664 log("Connection state " + mDevice + ": " + profileStateToString(prevState) 665 + "->" + profileStateToString(newState)); 666 667 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 668 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 669 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 670 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 671 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 672 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 673 mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 674 } 675 676 private void broadcastAudioState(int newState, int prevState) { 677 log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState) 678 + "->" + audioStateToString(newState)); 679 680 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); 681 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 682 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 683 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 684 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 685 mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); 686 } 687 688 @Override 689 protected String getLogRecString(Message msg) { 690 StringBuilder builder = new StringBuilder(); 691 builder.append(messageWhatToString(msg.what)); 692 builder.append(": "); 693 builder.append("arg1=") 694 .append(msg.arg1) 695 .append(", arg2=") 696 .append(msg.arg2) 697 .append(", obj=") 698 .append(msg.obj); 699 return builder.toString(); 700 } 701 702 private static String messageWhatToString(int what) { 703 switch (what) { 704 case CONNECT: 705 return "CONNECT"; 706 case DISCONNECT: 707 return "DISCONNECT"; 708 case STACK_EVENT: 709 return "STACK_EVENT"; 710 case CONNECT_TIMEOUT: 711 return "CONNECT_TIMEOUT"; 712 default: 713 break; 714 } 715 return Integer.toString(what); 716 } 717 718 private static String profileStateToString(int state) { 719 switch (state) { 720 case BluetoothProfile.STATE_DISCONNECTED: 721 return "DISCONNECTED"; 722 case BluetoothProfile.STATE_CONNECTING: 723 return "CONNECTING"; 724 case BluetoothProfile.STATE_CONNECTED: 725 return "CONNECTED"; 726 case BluetoothProfile.STATE_DISCONNECTING: 727 return "DISCONNECTING"; 728 default: 729 break; 730 } 731 return Integer.toString(state); 732 } 733 734 private static String audioStateToString(int state) { 735 switch (state) { 736 case BluetoothA2dp.STATE_PLAYING: 737 return "PLAYING"; 738 case BluetoothA2dp.STATE_NOT_PLAYING: 739 return "NOT_PLAYING"; 740 default: 741 break; 742 } 743 return Integer.toString(state); 744 } 745 746 public void dump(StringBuilder sb) { 747 ProfileService.println(sb, "mDevice: " + mDevice); 748 ProfileService.println(sb, " StateMachine: " + this.toString()); 749 ProfileService.println(sb, " mIsPlaying: " + mIsPlaying); 750 synchronized (this) { 751 if (mCodecStatus != null) { 752 ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig()); 753 } 754 } 755 ProfileService.println(sb, " StateMachine: " + this); 756 // Dump the state machine logs 757 StringWriter stringWriter = new StringWriter(); 758 PrintWriter printWriter = new PrintWriter(stringWriter); 759 super.dump(new FileDescriptor(), printWriter, new String[]{}); 760 printWriter.flush(); 761 stringWriter.flush(); 762 ProfileService.println(sb, " StateMachineLog:"); 763 Scanner scanner = new Scanner(stringWriter.toString()); 764 while (scanner.hasNextLine()) { 765 String line = scanner.nextLine(); 766 ProfileService.println(sb, " " + line); 767 } 768 scanner.close(); 769 } 770 771 @Override 772 protected void log(String msg) { 773 if (DBG) { 774 super.log(msg); 775 } 776 } 777 } 778