A2dpSinkStateMachine.java revision c4fbd756e2645147470c486ae96f2253f5e13a52
1/* 2 * Copyright (C) 2014 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 Sink StateMachine 19 * (Disconnected) 20 * | ^ 21 * CONNECT | | DISCONNECTED 22 * V | 23 * (Pending) 24 * | ^ 25 * CONNECTED | | CONNECT 26 * V | 27 * (Connected -- See A2dpSinkStreamHandler) 28 */ 29package com.android.bluetooth.a2dpsink; 30 31import android.bluetooth.BluetoothA2dpSink; 32import android.bluetooth.BluetoothAdapter; 33import android.bluetooth.BluetoothAudioConfig; 34import android.bluetooth.BluetoothDevice; 35import android.bluetooth.BluetoothProfile; 36import android.bluetooth.BluetoothUuid; 37import android.content.Context; 38import android.content.Intent; 39import android.media.AudioFormat; 40import android.os.Handler; 41import android.os.Message; 42import android.os.ParcelUuid; 43import android.os.PowerManager; 44import android.util.Log; 45 46import com.android.bluetooth.Utils; 47import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; 48import com.android.bluetooth.btservice.AdapterService; 49import com.android.bluetooth.btservice.ProfileService; 50import com.android.internal.util.IState; 51import com.android.internal.util.State; 52import com.android.internal.util.StateMachine; 53 54import java.util.ArrayList; 55import java.util.HashMap; 56import java.util.List; 57import java.util.Set; 58 59public class A2dpSinkStateMachine extends StateMachine { 60 private static final boolean DBG = false; 61 62 static final int CONNECT = 1; 63 static final int DISCONNECT = 2; 64 private static final int STACK_EVENT = 101; 65 private static final int CONNECT_TIMEOUT = 201; 66 public static final int EVENT_AVRCP_CT_PLAY = 301; 67 public static final int EVENT_AVRCP_CT_PAUSE = 302; 68 public static final int EVENT_AVRCP_TG_PLAY = 303; 69 public static final int EVENT_AVRCP_TG_PAUSE = 304; 70 71 private static final int IS_INVALID_DEVICE = 0; 72 private static final int IS_VALID_DEVICE = 1; 73 public static final int AVRC_ID_PLAY = 0x44; 74 public static final int AVRC_ID_PAUSE = 0x46; 75 public static final int KEY_STATE_PRESSED = 0; 76 public static final int KEY_STATE_RELEASED = 1; 77 78 // Connection states. 79 // 1. Disconnected: The connection does not exist. 80 // 2. Pending: The connection is being established. 81 // 3. Connected: The connection is established. The audio connection is in Idle state. 82 private Disconnected mDisconnected; 83 private Pending mPending; 84 private Connected mConnected; 85 86 private A2dpSinkService mService; 87 private Context mContext; 88 private BluetoothAdapter mAdapter; 89 private IntentBroadcastHandler mIntentBroadcastHandler; 90 91 private static final int MSG_CONNECTION_STATE_CHANGED = 0; 92 93 private final Object mLockForPatch = new Object(); 94 95 // mCurrentDevice is the device connected before the state changes 96 // mTargetDevice is the device to be connected 97 // mIncomingDevice is the device connecting to us, valid only in Pending state 98 // when mIncomingDevice is not null, both mCurrentDevice 99 // and mTargetDevice are null 100 // when either mCurrentDevice or mTargetDevice is not null, 101 // mIncomingDevice is null 102 // Stable states 103 // No connection, Disconnected state 104 // both mCurrentDevice and mTargetDevice are null 105 // Connected, Connected state 106 // mCurrentDevice is not null, mTargetDevice is null 107 // Interim states 108 // Connecting to a device, Pending 109 // mCurrentDevice is null, mTargetDevice is not null 110 // Disconnecting device, Connecting to new device 111 // Pending 112 // Both mCurrentDevice and mTargetDevice are not null 113 // Disconnecting device Pending 114 // mCurrentDevice is not null, mTargetDevice is null 115 // Incoming connections Pending 116 // Both mCurrentDevice and mTargetDevice are null 117 private BluetoothDevice mCurrentDevice = null; 118 private BluetoothDevice mTargetDevice = null; 119 private BluetoothDevice mIncomingDevice = null; 120 private BluetoothDevice mPlayingDevice = null; 121 private A2dpSinkStreamHandler mStreaming = null; 122 123 private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs = 124 new HashMap<BluetoothDevice, BluetoothAudioConfig>(); 125 126 static { 127 classInitNative(); 128 } 129 130 private A2dpSinkStateMachine(A2dpSinkService svc, Context context) { 131 super("A2dpSinkStateMachine"); 132 mService = svc; 133 mContext = context; 134 mAdapter = BluetoothAdapter.getDefaultAdapter(); 135 136 initNative(); 137 138 mDisconnected = new Disconnected(); 139 mPending = new Pending(); 140 mConnected = new Connected(); 141 142 addState(mDisconnected); 143 addState(mPending); 144 addState(mConnected); 145 146 setInitialState(mDisconnected); 147 148 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 149 150 mIntentBroadcastHandler = new IntentBroadcastHandler(); 151 } 152 153 static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) { 154 Log.d("A2dpSinkStateMachine", "make"); 155 A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context); 156 a2dpSm.start(); 157 return a2dpSm; 158 } 159 160 public void doQuit() { 161 if (DBG) { 162 Log.d("A2dpSinkStateMachine", "Quit"); 163 } 164 synchronized (A2dpSinkStateMachine.this) { 165 mStreaming = null; 166 } 167 quitNow(); 168 } 169 170 public void cleanup() { 171 cleanupNative(); 172 mAudioConfigs.clear(); 173 } 174 175 public void dump(StringBuilder sb) { 176 ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice); 177 ProfileService.println(sb, "mTargetDevice: " + mTargetDevice); 178 ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice); 179 ProfileService.println(sb, "StateMachine: " + this.toString()); 180 } 181 182 private class Disconnected extends State { 183 @Override 184 public void enter() { 185 log("Enter Disconnected: " + getCurrentMessage().what); 186 } 187 188 @Override 189 public boolean processMessage(Message message) { 190 log("Disconnected process message: " + message.what); 191 if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) { 192 loge("ERROR: current, target, or mIncomingDevice not null in Disconnected"); 193 return NOT_HANDLED; 194 } 195 196 boolean retValue = HANDLED; 197 switch (message.what) { 198 case CONNECT: 199 BluetoothDevice device = (BluetoothDevice) message.obj; 200 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 201 BluetoothProfile.STATE_DISCONNECTED); 202 203 if (!connectA2dpNative(getByteAddress(device))) { 204 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 205 BluetoothProfile.STATE_CONNECTING); 206 break; 207 } 208 209 synchronized (A2dpSinkStateMachine.this) { 210 mTargetDevice = device; 211 transitionTo(mPending); 212 } 213 // TODO(BT) remove CONNECT_TIMEOUT when the stack 214 // sends back events consistently 215 sendMessageDelayed(CONNECT_TIMEOUT, 30000); 216 break; 217 case DISCONNECT: 218 // ignore 219 break; 220 case STACK_EVENT: 221 StackEvent event = (StackEvent) message.obj; 222 switch (event.type) { 223 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 224 processConnectionEvent(event.valueInt, event.device); 225 break; 226 case EVENT_TYPE_AUDIO_CONFIG_CHANGED: 227 processAudioConfigEvent(event.audioConfig, event.device); 228 break; 229 default: 230 loge("Unexpected stack event: " + event.type); 231 break; 232 } 233 break; 234 default: 235 return NOT_HANDLED; 236 } 237 return retValue; 238 } 239 240 @Override 241 public void exit() { 242 log("Exit Disconnected: " + getCurrentMessage().what); 243 } 244 245 // in Disconnected state 246 private void processConnectionEvent(int state, BluetoothDevice device) { 247 switch (state) { 248 case CONNECTION_STATE_DISCONNECTED: 249 logw("Ignore A2DP DISCONNECTED event, device: " + device); 250 break; 251 case CONNECTION_STATE_CONNECTING: 252 if (okToConnect(device)) { 253 logi("Incoming A2DP accepted"); 254 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 255 BluetoothProfile.STATE_DISCONNECTED); 256 synchronized (A2dpSinkStateMachine.this) { 257 mIncomingDevice = device; 258 transitionTo(mPending); 259 } 260 } else { 261 //reject the connection and stay in Disconnected state itself 262 logi("Incoming A2DP rejected"); 263 disconnectA2dpNative(getByteAddress(device)); 264 } 265 break; 266 case CONNECTION_STATE_CONNECTED: 267 logw("A2DP Connected from Disconnected state"); 268 if (okToConnect(device)) { 269 logi("Incoming A2DP accepted"); 270 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 271 BluetoothProfile.STATE_DISCONNECTED); 272 synchronized (A2dpSinkStateMachine.this) { 273 mCurrentDevice = device; 274 transitionTo(mConnected); 275 } 276 } else { 277 //reject the connection and stay in Disconnected state itself 278 logi("Incoming A2DP rejected"); 279 disconnectA2dpNative(getByteAddress(device)); 280 } 281 break; 282 case CONNECTION_STATE_DISCONNECTING: 283 logw("Ignore HF DISCONNECTING event, device: " + device); 284 break; 285 default: 286 loge("Incorrect state: " + state); 287 break; 288 } 289 } 290 } 291 292 private class Pending extends State { 293 @Override 294 public void enter() { 295 log("Enter Pending: " + getCurrentMessage().what); 296 } 297 298 @Override 299 public boolean processMessage(Message message) { 300 log("Pending process message: " + message.what); 301 302 boolean retValue = HANDLED; 303 switch (message.what) { 304 case CONNECT: 305 deferMessage(message); 306 break; 307 case CONNECT_TIMEOUT: 308 onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED, 309 getByteAddress(mTargetDevice)); 310 break; 311 case DISCONNECT: 312 BluetoothDevice device = (BluetoothDevice) message.obj; 313 if (mCurrentDevice != null && mTargetDevice != null && mTargetDevice.equals( 314 device)) { 315 // cancel connection to the mTargetDevice 316 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 317 BluetoothProfile.STATE_CONNECTING); 318 synchronized (A2dpSinkStateMachine.this) { 319 mTargetDevice = null; 320 } 321 } else { 322 deferMessage(message); 323 } 324 break; 325 case STACK_EVENT: 326 StackEvent event = (StackEvent) message.obj; 327 log("STACK_EVENT " + event.type); 328 switch (event.type) { 329 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 330 removeMessages(CONNECT_TIMEOUT); 331 processConnectionEvent(event.valueInt, event.device); 332 break; 333 case EVENT_TYPE_AUDIO_CONFIG_CHANGED: 334 processAudioConfigEvent(event.audioConfig, event.device); 335 break; 336 default: 337 loge("Unexpected stack event: " + event.type); 338 break; 339 } 340 break; 341 default: 342 return NOT_HANDLED; 343 } 344 return retValue; 345 } 346 347 // in Pending state 348 private void processConnectionEvent(int state, BluetoothDevice device) { 349 log("processConnectionEvent state " + state); 350 log("Devices curr: " + mCurrentDevice + " target: " + mTargetDevice + " incoming: " 351 + mIncomingDevice + " device: " + device); 352 switch (state) { 353 case CONNECTION_STATE_DISCONNECTED: 354 mAudioConfigs.remove(device); 355 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 356 broadcastConnectionState(mCurrentDevice, 357 BluetoothProfile.STATE_DISCONNECTED, 358 BluetoothProfile.STATE_DISCONNECTING); 359 synchronized (A2dpSinkStateMachine.this) { 360 mCurrentDevice = null; 361 } 362 363 if (mTargetDevice != null) { 364 if (!connectA2dpNative(getByteAddress(mTargetDevice))) { 365 broadcastConnectionState(mTargetDevice, 366 BluetoothProfile.STATE_DISCONNECTED, 367 BluetoothProfile.STATE_CONNECTING); 368 synchronized (A2dpSinkStateMachine.this) { 369 mTargetDevice = null; 370 transitionTo(mDisconnected); 371 } 372 } 373 } else { 374 synchronized (A2dpSinkStateMachine.this) { 375 mIncomingDevice = null; 376 transitionTo(mDisconnected); 377 } 378 } 379 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 380 // outgoing connection failed 381 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, 382 BluetoothProfile.STATE_CONNECTING); 383 synchronized (A2dpSinkStateMachine.this) { 384 mTargetDevice = null; 385 transitionTo(mDisconnected); 386 } 387 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 388 broadcastConnectionState(mIncomingDevice, 389 BluetoothProfile.STATE_DISCONNECTED, 390 BluetoothProfile.STATE_CONNECTING); 391 synchronized (A2dpSinkStateMachine.this) { 392 mIncomingDevice = null; 393 transitionTo(mDisconnected); 394 } 395 } else { 396 loge("Unknown device Disconnected: " + device); 397 } 398 break; 399 case CONNECTION_STATE_CONNECTED: 400 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 401 loge("current device is not null"); 402 // disconnection failed 403 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, 404 BluetoothProfile.STATE_DISCONNECTING); 405 if (mTargetDevice != null) { 406 broadcastConnectionState(mTargetDevice, 407 BluetoothProfile.STATE_DISCONNECTED, 408 BluetoothProfile.STATE_CONNECTING); 409 } 410 synchronized (A2dpSinkStateMachine.this) { 411 mTargetDevice = null; 412 transitionTo(mConnected); 413 } 414 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 415 loge("target device is not null"); 416 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED, 417 BluetoothProfile.STATE_CONNECTING); 418 synchronized (A2dpSinkStateMachine.this) { 419 mCurrentDevice = mTargetDevice; 420 mTargetDevice = null; 421 transitionTo(mConnected); 422 } 423 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 424 loge("incoming device is not null"); 425 broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED, 426 BluetoothProfile.STATE_CONNECTING); 427 synchronized (A2dpSinkStateMachine.this) { 428 mCurrentDevice = mIncomingDevice; 429 mIncomingDevice = null; 430 transitionTo(mConnected); 431 } 432 } else { 433 loge("Unknown device Connected: " + device); 434 // something is wrong here, but sync our state with stack by connecting to 435 // the new device and disconnect from previous device. 436 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 437 BluetoothProfile.STATE_DISCONNECTED); 438 broadcastConnectionState(mCurrentDevice, 439 BluetoothProfile.STATE_DISCONNECTED, 440 BluetoothProfile.STATE_CONNECTING); 441 synchronized (A2dpSinkStateMachine.this) { 442 mCurrentDevice = device; 443 mTargetDevice = null; 444 mIncomingDevice = null; 445 transitionTo(mConnected); 446 } 447 } 448 break; 449 case CONNECTION_STATE_CONNECTING: 450 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 451 log("current device tries to connect back"); 452 // TODO(BT) ignore or reject 453 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 454 // The stack is connecting to target device or 455 // there is an incoming connection from the target device at the same time 456 // we already broadcasted the intent, doing nothing here 457 log("Stack and target device are connecting"); 458 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 459 loge("Another connecting event on the incoming device"); 460 } else { 461 // We get an incoming connecting request while Pending 462 // TODO(BT) is stack handing this case? let's ignore it for now 463 log("Incoming connection while pending, ignore"); 464 } 465 break; 466 case CONNECTION_STATE_DISCONNECTING: 467 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 468 // we already broadcasted the intent, doing nothing here 469 if (DBG) { 470 log("stack is disconnecting mCurrentDevice"); 471 } 472 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 473 loge("TargetDevice is getting disconnected"); 474 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 475 loge("IncomingDevice is getting disconnected"); 476 } else { 477 loge("Disconnecting unknown device: " + device); 478 } 479 break; 480 default: 481 loge("Incorrect state: " + state); 482 break; 483 } 484 } 485 486 } 487 488 private class Connected extends State { 489 @Override 490 public void enter() { 491 log("Enter Connected: " + getCurrentMessage().what); 492 // Upon connected, the audio starts out as stopped 493 broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING, 494 BluetoothA2dpSink.STATE_PLAYING); 495 synchronized (A2dpSinkStateMachine.this) { 496 if (mStreaming == null) { 497 if (DBG) { 498 log("Creating New A2dpSinkStreamHandler"); 499 } 500 mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext); 501 } 502 } 503 informAudioFocusStateNative(0); 504 } 505 506 @Override 507 public boolean processMessage(Message message) { 508 log("Connected process message: " + message.what); 509 if (mCurrentDevice == null) { 510 loge("ERROR: mCurrentDevice is null in Connected"); 511 return NOT_HANDLED; 512 } 513 514 switch (message.what) { 515 case CONNECT: { 516 BluetoothDevice device = (BluetoothDevice) message.obj; 517 if (mCurrentDevice.equals(device)) { 518 break; 519 } 520 521 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 522 BluetoothProfile.STATE_DISCONNECTED); 523 if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) { 524 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 525 BluetoothProfile.STATE_CONNECTING); 526 break; 527 } 528 529 synchronized (A2dpSinkStateMachine.this) { 530 mTargetDevice = device; 531 mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget(); 532 transitionTo(mPending); 533 } 534 } 535 break; 536 537 case DISCONNECT: { 538 BluetoothDevice device = (BluetoothDevice) message.obj; 539 if (!mCurrentDevice.equals(device)) { 540 break; 541 } 542 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING, 543 BluetoothProfile.STATE_CONNECTED); 544 if (!disconnectA2dpNative(getByteAddress(device))) { 545 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 546 BluetoothProfile.STATE_DISCONNECTED); 547 break; 548 } 549 mPlayingDevice = null; 550 mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget(); 551 transitionTo(mPending); 552 } 553 break; 554 555 case STACK_EVENT: 556 StackEvent event = (StackEvent) message.obj; 557 switch (event.type) { 558 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 559 processConnectionEvent(event.valueInt, event.device); 560 break; 561 case EVENT_TYPE_AUDIO_STATE_CHANGED: 562 processAudioStateEvent(event.valueInt, event.device); 563 break; 564 case EVENT_TYPE_AUDIO_CONFIG_CHANGED: 565 processAudioConfigEvent(event.audioConfig, event.device); 566 break; 567 default: 568 loge("Unexpected stack event: " + event.type); 569 break; 570 } 571 break; 572 573 case EVENT_AVRCP_CT_PLAY: 574 mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY).sendToTarget(); 575 break; 576 577 case EVENT_AVRCP_TG_PLAY: 578 mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY).sendToTarget(); 579 break; 580 581 case EVENT_AVRCP_CT_PAUSE: 582 mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE).sendToTarget(); 583 break; 584 585 case EVENT_AVRCP_TG_PAUSE: 586 mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget(); 587 break; 588 589 default: 590 return NOT_HANDLED; 591 } 592 return HANDLED; 593 } 594 595 // in Connected state 596 private void processConnectionEvent(int state, BluetoothDevice device) { 597 switch (state) { 598 case CONNECTION_STATE_DISCONNECTED: 599 mAudioConfigs.remove(device); 600 if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) { 601 mPlayingDevice = null; 602 } 603 if (mCurrentDevice.equals(device)) { 604 broadcastConnectionState(mCurrentDevice, 605 BluetoothProfile.STATE_DISCONNECTED, 606 BluetoothProfile.STATE_CONNECTED); 607 synchronized (A2dpSinkStateMachine.this) { 608 // Take care of existing audio focus in the streaming state machine. 609 mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT) 610 .sendToTarget(); 611 mCurrentDevice = null; 612 transitionTo(mDisconnected); 613 } 614 } else { 615 loge("Disconnected from unknown device: " + device); 616 } 617 break; 618 default: 619 loge("Connection State Device: " + device + " bad state: " + state); 620 break; 621 } 622 } 623 624 private void processAudioStateEvent(int state, BluetoothDevice device) { 625 if (!mCurrentDevice.equals(device)) { 626 loge("Audio State Device:" + device + "is different from ConnectedDevice:" 627 + mCurrentDevice); 628 return; 629 } 630 log(" processAudioStateEvent in state " + state); 631 switch (state) { 632 case AUDIO_STATE_STARTED: 633 mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget(); 634 break; 635 case AUDIO_STATE_REMOTE_SUSPEND: 636 case AUDIO_STATE_STOPPED: 637 mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget(); 638 break; 639 default: 640 loge("Audio State Device: " + device + " bad state: " + state); 641 break; 642 } 643 } 644 } 645 646 private void processAudioConfigEvent(BluetoothAudioConfig audioConfig, BluetoothDevice device) { 647 log("processAudioConfigEvent: " + device); 648 mAudioConfigs.put(device, audioConfig); 649 broadcastAudioConfig(device, audioConfig); 650 } 651 652 int getConnectionState(BluetoothDevice device) { 653 if (getCurrentState() == mDisconnected) { 654 return BluetoothProfile.STATE_DISCONNECTED; 655 } 656 657 synchronized (this) { 658 IState currentState = getCurrentState(); 659 if (currentState == mPending) { 660 if ((mTargetDevice != null) && mTargetDevice.equals(device)) { 661 return BluetoothProfile.STATE_CONNECTING; 662 } 663 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 664 return BluetoothProfile.STATE_DISCONNECTING; 665 } 666 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) { 667 return BluetoothProfile.STATE_CONNECTING; // incoming connection 668 } 669 return BluetoothProfile.STATE_DISCONNECTED; 670 } 671 672 if (currentState == mConnected) { 673 if (mCurrentDevice.equals(device)) { 674 return BluetoothProfile.STATE_CONNECTED; 675 } 676 return BluetoothProfile.STATE_DISCONNECTED; 677 } else { 678 loge("Bad currentState: " + currentState); 679 return BluetoothProfile.STATE_DISCONNECTED; 680 } 681 } 682 } 683 684 BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { 685 return mAudioConfigs.get(device); 686 } 687 688 List<BluetoothDevice> getConnectedDevices() { 689 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 690 synchronized (this) { 691 if (getCurrentState() == mConnected) { 692 devices.add(mCurrentDevice); 693 } 694 } 695 return devices; 696 } 697 698 boolean isPlaying(BluetoothDevice device) { 699 synchronized (this) { 700 if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) { 701 return true; 702 } 703 } 704 return false; 705 } 706 707 // Utility Functions 708 boolean okToConnect(BluetoothDevice device) { 709 AdapterService adapterService = AdapterService.getAdapterService(); 710 int priority = mService.getPriority(device); 711 712 // check priority and accept or reject the connection. if priority is undefined 713 // it is likely that our SDP has not completed and peer is initiating the 714 // connection. Allow this connection, provided the device is bonded 715 if ((BluetoothProfile.PRIORITY_OFF < priority) || ( 716 (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState() 717 != BluetoothDevice.BOND_NONE))) { 718 return true; 719 } 720 logw("okToConnect not OK to connect " + device); 721 return false; 722 } 723 724 synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 725 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); 726 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 727 int connectionState; 728 729 for (BluetoothDevice device : bondedDevices) { 730 ParcelUuid[] featureUuids = device.getUuids(); 731 if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) { 732 continue; 733 } 734 connectionState = getConnectionState(device); 735 for (int i = 0; i < states.length; i++) { 736 if (connectionState == states[i]) { 737 deviceList.add(device); 738 } 739 } 740 } 741 return deviceList; 742 } 743 744 745 // This method does not check for error conditon (newState == prevState) 746 private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) { 747 748 int delay = 0; 749 mIntentBroadcastHandler.sendMessageDelayed( 750 mIntentBroadcastHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGED, prevState, 751 newState, device), delay); 752 } 753 754 private void broadcastAudioState(BluetoothDevice device, int state, int prevState) { 755 Intent intent = new Intent(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED); 756 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 757 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 758 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 759//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 760 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 761 762 log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); 763 } 764 765 private void broadcastAudioConfig(BluetoothDevice device, BluetoothAudioConfig audioConfig) { 766 Intent intent = new Intent(BluetoothA2dpSink.ACTION_AUDIO_CONFIG_CHANGED); 767 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 768 intent.putExtra(BluetoothA2dpSink.EXTRA_AUDIO_CONFIG, audioConfig); 769//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 770 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 771 772 log("A2DP Audio Config : device: " + device + " config: " + audioConfig); 773 } 774 775 private byte[] getByteAddress(BluetoothDevice device) { 776 return Utils.getBytesFromAddress(device.getAddress()); 777 } 778 779 private void onConnectionStateChanged(int state, byte[] address) { 780 StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED); 781 event.valueInt = state; 782 event.device = getDevice(address); 783 sendMessage(STACK_EVENT, event); 784 } 785 786 private void onAudioStateChanged(int state, byte[] address) { 787 StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED); 788 event.valueInt = state; 789 event.device = getDevice(address); 790 sendMessage(STACK_EVENT, event); 791 } 792 793 private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) { 794 StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_CONFIG_CHANGED); 795 int channelConfig = 796 (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO); 797 event.audioConfig = 798 new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT); 799 event.device = getDevice(address); 800 sendMessage(STACK_EVENT, event); 801 } 802 803 private BluetoothDevice getDevice(byte[] address) { 804 return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 805 } 806 807 private class StackEvent { 808 public int type = EVENT_TYPE_NONE; 809 public int valueInt = 0; 810 public BluetoothDevice device = null; 811 public BluetoothAudioConfig audioConfig = null; 812 813 private StackEvent(int type) { 814 this.type = type; 815 } 816 } 817 818 /** Handles A2DP connection state change intent broadcasts. */ 819 private class IntentBroadcastHandler extends Handler { 820 821 private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { 822 Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 823 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 824 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 825 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 826//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 827 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 828 log("Connection state " + device + ": " + prevState + "->" + state); 829 } 830 831 @Override 832 public void handleMessage(Message msg) { 833 switch (msg.what) { 834 case MSG_CONNECTION_STATE_CHANGED: 835 onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2); 836 break; 837 } 838 } 839 } 840 841 public boolean sendPassThruPlay(BluetoothDevice mDevice) { 842 log("sendPassThruPlay + "); 843 AvrcpControllerService avrcpCtrlService = 844 AvrcpControllerService.getAvrcpControllerService(); 845 if ((avrcpCtrlService != null) && (mDevice != null) 846 && (avrcpCtrlService.getConnectedDevices().contains(mDevice))) { 847 avrcpCtrlService.sendPassThroughCmd(mDevice, 848 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 849 AvrcpControllerService.KEY_STATE_PRESSED); 850 avrcpCtrlService.sendPassThroughCmd(mDevice, 851 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 852 AvrcpControllerService.KEY_STATE_RELEASED); 853 log(" sendPassThruPlay command sent - "); 854 return true; 855 } else { 856 log("passthru command not sent, connection unavailable"); 857 return false; 858 } 859 } 860 861 // Event types for STACK_EVENT message 862 private static final int EVENT_TYPE_NONE = 0; 863 private static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; 864 private static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2; 865 private static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3; 866 867 // Do not modify without updating the HAL bt_av.h files. 868 869 // match up with btav_connection_state_t enum of bt_av.h 870 static final int CONNECTION_STATE_DISCONNECTED = 0; 871 static final int CONNECTION_STATE_CONNECTING = 1; 872 static final int CONNECTION_STATE_CONNECTED = 2; 873 static final int CONNECTION_STATE_DISCONNECTING = 3; 874 875 // match up with btav_audio_state_t enum of bt_av.h 876 static final int AUDIO_STATE_REMOTE_SUSPEND = 0; 877 static final int AUDIO_STATE_STOPPED = 1; 878 static final int AUDIO_STATE_STARTED = 2; 879 880 private static native void classInitNative(); 881 882 private native void initNative(); 883 884 private native void cleanupNative(); 885 886 private native boolean connectA2dpNative(byte[] address); 887 888 private native boolean disconnectA2dpNative(byte[] address); 889 890 public native void informAudioFocusStateNative(int focusGranted); 891 892 public native void informAudioTrackGainNative(float focusGranted); 893} 894