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