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