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