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