A2dpStateMachine.java revision 25ec7de5ff422f50f3dbfd0822423c85b47f4794
1/* 2 * Copyright (C) 2012 Google Inc. 3 */ 4 5/** 6 * Bluetooth A2dp StateMachine 7 * (Disconnected) 8 * | ^ 9 * CONNECT | | DISCONNECTED 10 * V | 11 * (Pending) 12 * | ^ 13 * CONNECTED | | CONNECT 14 * V | 15 * (Connected) 16 */ 17package com.android.bluetooth.a2dp; 18 19import android.bluetooth.BluetoothA2dp; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothProfile; 23import android.bluetooth.BluetoothUuid; 24import android.bluetooth.IBluetooth; 25import android.content.Context; 26import android.content.Intent; 27import android.os.Message; 28import android.os.RemoteException; 29import android.os.ServiceManager; 30import android.os.ParcelUuid; 31import android.util.Log; 32import com.android.bluetooth.Utils; 33import com.android.internal.util.IState; 34import com.android.internal.util.State; 35import com.android.internal.util.StateMachine; 36import java.util.ArrayList; 37import java.util.List; 38import java.util.Set; 39 40final class A2dpStateMachine extends StateMachine { 41 private static final String TAG = "A2dpStateMachine"; 42 private static final boolean DBG = true; 43 44 static final int CONNECT = 1; 45 static final int DISCONNECT = 2; 46 private static final int STACK_EVENT = 101; 47 private static final int CONNECT_TIMEOUT = 201; 48 49 private Disconnected mDisconnected; 50 private Pending mPending; 51 private Connected mConnected; 52 53 private Context mContext; 54 private BluetoothAdapter mAdapter; 55 private IBluetooth mAdapterService; 56 private static final ParcelUuid[] A2DP_UUIDS = { 57 BluetoothUuid.AudioSink 58 }; 59 60 // mCurrentDevice is the device connected before the state changes 61 // mTargetDevice is the device to be connected 62 // mIncomingDevice is the device connecting to us, valid only in Pending state 63 // when mIncomingDevice is not null, both mCurrentDevice 64 // and mTargetDevice are null 65 // when either mCurrentDevice or mTargetDevice is not null, 66 // mIncomingDevice is null 67 // Stable states 68 // No connection, Disconnected state 69 // both mCurrentDevice and mTargetDevice are null 70 // Connected, Connected state 71 // mCurrentDevice is not null, mTargetDevice is null 72 // Interim states 73 // Connecting to a device, Pending 74 // mCurrentDevice is null, mTargetDevice is not null 75 // Disconnecting device, Connecting to new device 76 // Pending 77 // Both mCurrentDevice and mTargetDevice are not null 78 // Disconnecting device Pending 79 // mCurrentDevice is not null, mTargetDevice is null 80 // Incoming connections Pending 81 // Both mCurrentDevice and mTargetDevice are null 82 private BluetoothDevice mCurrentDevice = null; 83 private BluetoothDevice mTargetDevice = null; 84 private BluetoothDevice mIncomingDevice = null; 85 86 static { 87 classInitNative(); 88 } 89 90 A2dpStateMachine(Context context) { 91 super(TAG); 92 mContext = context; 93 mAdapter = BluetoothAdapter.getDefaultAdapter(); 94 mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth")); 95 96 initializeNativeDataNative(); 97 98 mDisconnected = new Disconnected(); 99 mPending = new Pending(); 100 mConnected = new Connected(); 101 102 addState(mDisconnected); 103 addState(mPending); 104 addState(mConnected); 105 106 setInitialState(mDisconnected); 107 } 108 109 private class Disconnected extends State { 110 @Override 111 public void enter() { 112 log("Enter Disconnected: " + getCurrentMessage().what); 113 } 114 115 @Override 116 public boolean processMessage(Message message) { 117 log("Disconnected process message: " + message.what); 118 if (DBG) { 119 if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) { 120 log("ERROR: current, target, or mIncomingDevice not null in Disconnected"); 121 return NOT_HANDLED; 122 } 123 } 124 125 boolean retValue = HANDLED; 126 switch(message.what) { 127 case CONNECT: 128 BluetoothDevice device = (BluetoothDevice) message.obj; 129 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 130 BluetoothProfile.STATE_DISCONNECTED); 131 132 if (!connectA2dpNative(getByteAddress(device)) ) { 133 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 134 BluetoothProfile.STATE_CONNECTING); 135 break; 136 } 137 138 synchronized (A2dpStateMachine.this) { 139 mTargetDevice = device; 140 transitionTo(mPending); 141 } 142 // TODO(BT) remove CONNECT_TIMEOUT when the stack 143 // sends back events consistently 144 sendMessageDelayed(CONNECT_TIMEOUT, 30000); 145 break; 146 case DISCONNECT: 147 // ignore 148 break; 149 case STACK_EVENT: 150 StackEvent event = (StackEvent) message.obj; 151 switch (event.type) { 152 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 153 processConnectionEvent(event.valueInt, event.device); 154 break; 155 default: 156 Log.e(TAG, "Unexpected stack event: " + event.type); 157 break; 158 } 159 break; 160 default: 161 return NOT_HANDLED; 162 } 163 return retValue; 164 } 165 166 @Override 167 public void exit() { 168 log("Exit Disconnected: " + getCurrentMessage().what); 169 } 170 171 // in Disconnected state 172 private void processConnectionEvent(int state, BluetoothDevice device) { 173 switch (state) { 174 case CONNECTION_STATE_DISCONNECTED: 175 Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device); 176 break; 177 case CONNECTION_STATE_CONNECTING: 178 // TODO(BT) Assume it's incoming connection 179 // Do we need to check priority and accept/reject accordingly? 180 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 181 BluetoothProfile.STATE_DISCONNECTED); 182 synchronized (A2dpStateMachine.this) { 183 mIncomingDevice = device; 184 transitionTo(mPending); 185 } 186 break; 187 case CONNECTION_STATE_CONNECTED: 188 Log.w(TAG, "A2DP Connected from Disconnected state"); 189 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 190 BluetoothProfile.STATE_DISCONNECTED); 191 synchronized (A2dpStateMachine.this) { 192 mCurrentDevice = device; 193 transitionTo(mConnected); 194 } 195 break; 196 case CONNECTION_STATE_DISCONNECTING: 197 Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device); 198 break; 199 default: 200 Log.e(TAG, "Incorrect state: " + state); 201 break; 202 } 203 } 204 } 205 206 private class Pending extends State { 207 @Override 208 public void enter() { 209 log("Enter Pending: " + getCurrentMessage().what); 210 } 211 212 @Override 213 public boolean processMessage(Message message) { 214 log("Pending process message: " + message.what); 215 216 boolean retValue = HANDLED; 217 switch(message.what) { 218 case CONNECT: 219 deferMessage(message); 220 break; 221 case CONNECT_TIMEOUT: 222 onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED, 223 getByteAddress(mTargetDevice)); 224 break; 225 case DISCONNECT: 226 BluetoothDevice device = (BluetoothDevice) message.obj; 227 if (mCurrentDevice != null && mTargetDevice != null && 228 mTargetDevice.equals(device) ) { 229 // cancel connection to the mTargetDevice 230 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 231 BluetoothProfile.STATE_CONNECTING); 232 synchronized (A2dpStateMachine.this) { 233 mTargetDevice = null; 234 } 235 } else { 236 deferMessage(message); 237 } 238 break; 239 case STACK_EVENT: 240 StackEvent event = (StackEvent) message.obj; 241 switch (event.type) { 242 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 243 removeMessages(CONNECT_TIMEOUT); 244 processConnectionEvent(event.valueInt, event.device); 245 break; 246 default: 247 Log.e(TAG, "Unexpected stack event: " + event.type); 248 break; 249 } 250 break; 251 default: 252 return NOT_HANDLED; 253 } 254 return retValue; 255 } 256 257 // in Pending state 258 private void processConnectionEvent(int state, BluetoothDevice device) { 259 switch (state) { 260 case CONNECTION_STATE_DISCONNECTED: 261 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 262 broadcastConnectionState(mCurrentDevice, 263 BluetoothProfile.STATE_DISCONNECTED, 264 BluetoothProfile.STATE_DISCONNECTING); 265 synchronized (A2dpStateMachine.this) { 266 mCurrentDevice = null; 267 } 268 269 if (mTargetDevice != null) { 270 if (!connectA2dpNative(getByteAddress(mTargetDevice))) { 271 broadcastConnectionState(mTargetDevice, 272 BluetoothProfile.STATE_DISCONNECTED, 273 BluetoothProfile.STATE_CONNECTING); 274 synchronized (A2dpStateMachine.this) { 275 mTargetDevice = null; 276 transitionTo(mDisconnected); 277 } 278 } 279 } else { 280 synchronized (A2dpStateMachine.this) { 281 mIncomingDevice = null; 282 transitionTo(mDisconnected); 283 } 284 } 285 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 286 // outgoing connection failed 287 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, 288 BluetoothProfile.STATE_CONNECTING); 289 synchronized (A2dpStateMachine.this) { 290 mTargetDevice = null; 291 transitionTo(mDisconnected); 292 } 293 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 294 broadcastConnectionState(mIncomingDevice, 295 BluetoothProfile.STATE_DISCONNECTED, 296 BluetoothProfile.STATE_CONNECTING); 297 synchronized (A2dpStateMachine.this) { 298 mIncomingDevice = null; 299 transitionTo(mDisconnected); 300 } 301 } else { 302 Log.e(TAG, "Unknown device Disconnected: " + device); 303 } 304 break; 305 case CONNECTION_STATE_CONNECTED: 306 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 307 // disconnection failed 308 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, 309 BluetoothProfile.STATE_DISCONNECTING); 310 if (mTargetDevice != null) { 311 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, 312 BluetoothProfile.STATE_CONNECTING); 313 } 314 synchronized (A2dpStateMachine.this) { 315 mTargetDevice = null; 316 transitionTo(mConnected); 317 } 318 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 319 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED, 320 BluetoothProfile.STATE_CONNECTING); 321 synchronized (A2dpStateMachine.this) { 322 mCurrentDevice = mTargetDevice; 323 mTargetDevice = null; 324 transitionTo(mConnected); 325 } 326 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 327 broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED, 328 BluetoothProfile.STATE_CONNECTING); 329 synchronized (A2dpStateMachine.this) { 330 mCurrentDevice = mIncomingDevice; 331 mIncomingDevice = null; 332 transitionTo(mConnected); 333 } 334 } else { 335 Log.e(TAG, "Unknown device Connected: " + device); 336 // something is wrong here, but sync our state with stack 337 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 338 BluetoothProfile.STATE_DISCONNECTED); 339 synchronized (A2dpStateMachine.this) { 340 mCurrentDevice = device; 341 mTargetDevice = null; 342 mIncomingDevice = null; 343 transitionTo(mConnected); 344 } 345 } 346 break; 347 case CONNECTION_STATE_CONNECTING: 348 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 349 log("current device tries to connect back"); 350 // TODO(BT) ignore or reject 351 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 352 // The stack is connecting to target device or 353 // there is an incoming connection from the target device at the same time 354 // we already broadcasted the intent, doing nothing here 355 if (DBG) { 356 log("Stack and target device are connecting"); 357 } 358 } 359 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 360 Log.e(TAG, "Another connecting event on the incoming device"); 361 } else { 362 // We get an incoming connecting request while Pending 363 // TODO(BT) is stack handing this case? let's ignore it for now 364 log("Incoming connection while pending, ignore"); 365 } 366 break; 367 case CONNECTION_STATE_DISCONNECTING: 368 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 369 // we already broadcasted the intent, doing nothing here 370 if (DBG) { 371 log("stack is disconnecting mCurrentDevice"); 372 } 373 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 374 Log.e(TAG, "TargetDevice is getting disconnected"); 375 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 376 Log.e(TAG, "IncomingDevice is getting disconnected"); 377 } else { 378 Log.e(TAG, "Disconnecting unknow device: " + device); 379 } 380 break; 381 default: 382 Log.e(TAG, "Incorrect state: " + state); 383 break; 384 } 385 } 386 387 } 388 389 private class Connected extends State { 390 @Override 391 public void enter() { 392 log("Enter Connected: " + getCurrentMessage().what); 393 } 394 395 @Override 396 public boolean processMessage(Message message) { 397 log("Connected process message: " + message.what); 398 if (DBG) { 399 if (mCurrentDevice == null) { 400 log("ERROR: mCurrentDevice is null in Connected"); 401 return NOT_HANDLED; 402 } 403 } 404 405 boolean retValue = HANDLED; 406 switch(message.what) { 407 case CONNECT: 408 { 409 BluetoothDevice device = (BluetoothDevice) message.obj; 410 if (mCurrentDevice.equals(device)) { 411 break; 412 } 413 414 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 415 BluetoothProfile.STATE_DISCONNECTED); 416 if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) { 417 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 418 BluetoothProfile.STATE_CONNECTING); 419 break; 420 } 421 422 synchronized (A2dpStateMachine.this) { 423 mTargetDevice = device; 424 transitionTo(mPending); 425 } 426 } 427 break; 428 case DISCONNECT: 429 { 430 BluetoothDevice device = (BluetoothDevice) message.obj; 431 if (!mCurrentDevice.equals(device)) { 432 break; 433 } 434 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING, 435 BluetoothProfile.STATE_CONNECTED); 436 if (!disconnectA2dpNative(getByteAddress(device))) { 437 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 438 BluetoothProfile.STATE_DISCONNECTED); 439 break; 440 } 441 transitionTo(mPending); 442 } 443 break; 444 case STACK_EVENT: 445 StackEvent event = (StackEvent) message.obj; 446 switch (event.type) { 447 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 448 processConnectionEvent(event.valueInt, event.device); 449 break; 450 default: 451 Log.e(TAG, "Unexpected stack event: " + event.type); 452 break; 453 } 454 break; 455 default: 456 return NOT_HANDLED; 457 } 458 return retValue; 459 } 460 461 // in Connected state 462 private void processConnectionEvent(int state, BluetoothDevice device) { 463 switch (state) { 464 case CONNECTION_STATE_DISCONNECTED: 465 if (mCurrentDevice.equals(device)) { 466 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, 467 BluetoothProfile.STATE_CONNECTED); 468 synchronized (A2dpStateMachine.this) { 469 mCurrentDevice = null; 470 transitionTo(mDisconnected); 471 } 472 } else { 473 Log.e(TAG, "Disconnected from unknown device: " + device); 474 } 475 break; 476 default: 477 Log.e(TAG, "Connection State Device: " + device + " bad state: " + state); 478 break; 479 } 480 } 481 } 482 483 int getConnectionState(BluetoothDevice device) { 484 if (getCurrentState() == mDisconnected) { 485 return BluetoothProfile.STATE_DISCONNECTED; 486 } 487 488 synchronized (this) { 489 IState currentState = getCurrentState(); 490 if (currentState == mPending) { 491 if ((mTargetDevice != null) && mTargetDevice.equals(device)) { 492 return BluetoothProfile.STATE_CONNECTING; 493 } 494 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 495 return BluetoothProfile.STATE_DISCONNECTING; 496 } 497 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) { 498 return BluetoothProfile.STATE_CONNECTING; // incoming connection 499 } 500 return BluetoothProfile.STATE_DISCONNECTED; 501 } 502 503 if (currentState == mConnected) { 504 if (mCurrentDevice.equals(device)) { 505 return BluetoothProfile.STATE_CONNECTED; 506 } 507 return BluetoothProfile.STATE_DISCONNECTED; 508 } else { 509 Log.e(TAG, "Bad currentState: " + currentState); 510 return BluetoothProfile.STATE_DISCONNECTED; 511 } 512 } 513 } 514 515 List<BluetoothDevice> getConnectedDevices() { 516 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 517 synchronized(this) { 518 if (getCurrentState() == mConnected) { 519 devices.add(mCurrentDevice); 520 } 521 } 522 return devices; 523 } 524 525 boolean isPlaying(BluetoothDevice device) { 526 synchronized(this) { 527 if (getCurrentState() == mConnected && mCurrentDevice.equals(device)) { 528 return true; 529 } 530 } 531 return false; 532 } 533 534 synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 535 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); 536 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 537 int connectionState; 538 539 for (BluetoothDevice device : bondedDevices) { 540 ParcelUuid[] featureUuids = device.getUuids(); 541 if (!BluetoothUuid.containsAnyUuid(featureUuids, A2DP_UUIDS)) { 542 continue; 543 } 544 connectionState = getConnectionState(device); 545 for(int i = 0; i < states.length; i++) { 546 if (connectionState == states[i]) { 547 deviceList.add(device); 548 } 549 } 550 } 551 return deviceList; 552 } 553 554 // This method does not check for error conditon (newState == prevState) 555 private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) { 556 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 557 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 558 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 559 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 560 mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); 561 if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState); 562 try { 563 mAdapterService.sendConnectionStateChange(device, BluetoothProfile.A2DP, newState, 564 prevState); 565 } catch (RemoteException e) { 566 Log.e(TAG, Log.getStackTraceString(new Throwable())); 567 } 568 } 569 570 private byte[] getByteAddress(BluetoothDevice device) { 571 return Utils.getBytesFromAddress(device.getAddress()); 572 } 573 574 private void onConnectionStateChanged(int state, byte[] address) { 575 StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED); 576 event.valueInt = state; 577 event.device = getDevice(address); 578 sendMessage(STACK_EVENT, event); 579 } 580 581 private BluetoothDevice getDevice(byte[] address) { 582 return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 583 } 584 585 private void log(String msg) { 586 if (DBG) { 587 Log.d(TAG, msg); 588 } 589 } 590 591 private class StackEvent { 592 int type = EVENT_TYPE_NONE; 593 int valueInt = 0; 594 BluetoothDevice device = null; 595 596 private StackEvent(int type) { 597 this.type = type; 598 } 599 } 600 601 // Event types for STACK_EVENT message 602 final private static int EVENT_TYPE_NONE = 0; 603 final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; 604 605 // Do not modify without upating the HAL bt_av.h files. 606 607 // match up with btav_connection_state_t enum of bt_av.h 608 final static int CONNECTION_STATE_DISCONNECTED = 0; 609 final static int CONNECTION_STATE_CONNECTING = 1; 610 final static int CONNECTION_STATE_CONNECTED = 2; 611 final static int CONNECTION_STATE_DISCONNECTING = 3; 612 613 private native static void classInitNative(); 614 private native void initializeNativeDataNative(); 615 private native boolean connectA2dpNative(byte[] address); 616 private native boolean disconnectA2dpNative(byte[] address); 617} 618