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
140    static A2dpStateMachine make(A2dpService svc, Context context) {
141        Log.d("A2dpStateMachine", "make");
142        A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
143        a2dpSm.start();
144        return a2dpSm;
145    }
146
147    public void doQuit() {
148        quitNow();
149    }
150
151    public void cleanup() {
152        cleanupNative();
153    }
154
155        private class Disconnected extends State {
156        @Override
157        public void enter() {
158            log("Enter Disconnected: " + getCurrentMessage().what);
159        }
160
161        @Override
162        public boolean processMessage(Message message) {
163            log("Disconnected process message: " + message.what);
164            if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
165                loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
166                return NOT_HANDLED;
167            }
168
169            boolean retValue = HANDLED;
170            switch(message.what) {
171                case CONNECT:
172                    BluetoothDevice device = (BluetoothDevice) message.obj;
173                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
174                                   BluetoothProfile.STATE_DISCONNECTED);
175
176                    if (!connectA2dpNative(getByteAddress(device)) ) {
177                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
178                                       BluetoothProfile.STATE_CONNECTING);
179                        break;
180                    }
181
182                    synchronized (A2dpStateMachine.this) {
183                        mTargetDevice = device;
184                        transitionTo(mPending);
185                    }
186                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
187                    //          sends back events consistently
188                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
189                    break;
190                case DISCONNECT:
191                    // ignore
192                    break;
193                case STACK_EVENT:
194                    StackEvent event = (StackEvent) message.obj;
195                    switch (event.type) {
196                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
197                            processConnectionEvent(event.valueInt, event.device);
198                            break;
199                        default:
200                            loge("Unexpected stack event: " + event.type);
201                            break;
202                    }
203                    break;
204                default:
205                    return NOT_HANDLED;
206            }
207            return retValue;
208        }
209
210        @Override
211        public void exit() {
212            log("Exit Disconnected: " + getCurrentMessage().what);
213        }
214
215        // in Disconnected state
216        private void processConnectionEvent(int state, BluetoothDevice device) {
217            switch (state) {
218            case CONNECTION_STATE_DISCONNECTED:
219                logw("Ignore HF DISCONNECTED event, device: " + device);
220                break;
221            case CONNECTION_STATE_CONNECTING:
222                if (okToConnect(device)){
223                    logi("Incoming A2DP accepted");
224                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
225                                             BluetoothProfile.STATE_DISCONNECTED);
226                    synchronized (A2dpStateMachine.this) {
227                        mIncomingDevice = device;
228                        transitionTo(mPending);
229                    }
230                } else {
231                    //reject the connection and stay in Disconnected state itself
232                    logi("Incoming A2DP rejected");
233                    disconnectA2dpNative(getByteAddress(device));
234                    // the other profile connection should be initiated
235                    AdapterService adapterService = AdapterService.getAdapterService();
236                    if (adapterService != null) {
237                        adapterService.connectOtherProfile(device,
238                                                           AdapterService.PROFILE_CONN_REJECTED);
239                    }
240                }
241                break;
242            case CONNECTION_STATE_CONNECTED:
243                logw("A2DP Connected from Disconnected state");
244                if (okToConnect(device)){
245                    logi("Incoming A2DP accepted");
246                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
247                                             BluetoothProfile.STATE_DISCONNECTED);
248                    synchronized (A2dpStateMachine.this) {
249                        mCurrentDevice = device;
250                        transitionTo(mConnected);
251                    }
252                } else {
253                    //reject the connection and stay in Disconnected state itself
254                    logi("Incoming A2DP rejected");
255                    disconnectA2dpNative(getByteAddress(device));
256                    // the other profile connection should be initiated
257                    AdapterService adapterService = AdapterService.getAdapterService();
258                    if (adapterService != null) {
259                        adapterService.connectOtherProfile(device,
260                                                           AdapterService.PROFILE_CONN_REJECTED);
261                    }
262                }
263                break;
264            case CONNECTION_STATE_DISCONNECTING:
265                logw("Ignore HF DISCONNECTING event, device: " + device);
266                break;
267            default:
268                loge("Incorrect state: " + state);
269                break;
270            }
271        }
272    }
273
274    private class Pending extends State {
275        @Override
276        public void enter() {
277            log("Enter Pending: " + getCurrentMessage().what);
278        }
279
280        @Override
281        public boolean processMessage(Message message) {
282            log("Pending process message: " + message.what);
283
284            boolean retValue = HANDLED;
285            switch(message.what) {
286                case CONNECT:
287                    deferMessage(message);
288                    break;
289                case CONNECT_TIMEOUT:
290                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
291                                             getByteAddress(mTargetDevice));
292                    break;
293                case DISCONNECT:
294                    BluetoothDevice device = (BluetoothDevice) message.obj;
295                    if (mCurrentDevice != null && mTargetDevice != null &&
296                        mTargetDevice.equals(device) ) {
297                        // cancel connection to the mTargetDevice
298                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
299                                       BluetoothProfile.STATE_CONNECTING);
300                        synchronized (A2dpStateMachine.this) {
301                            mTargetDevice = null;
302                        }
303                    } else {
304                        deferMessage(message);
305                    }
306                    break;
307                case STACK_EVENT:
308                    StackEvent event = (StackEvent) message.obj;
309                    switch (event.type) {
310                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
311                            removeMessages(CONNECT_TIMEOUT);
312                            processConnectionEvent(event.valueInt, event.device);
313                            break;
314                        default:
315                            loge("Unexpected stack event: " + event.type);
316                            break;
317                    }
318                    break;
319                default:
320                    return NOT_HANDLED;
321            }
322            return retValue;
323        }
324
325        // in Pending state
326        private void processConnectionEvent(int state, BluetoothDevice device) {
327            switch (state) {
328                case CONNECTION_STATE_DISCONNECTED:
329                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
330                        broadcastConnectionState(mCurrentDevice,
331                                                 BluetoothProfile.STATE_DISCONNECTED,
332                                                 BluetoothProfile.STATE_DISCONNECTING);
333                        synchronized (A2dpStateMachine.this) {
334                            mCurrentDevice = null;
335                        }
336
337                        if (mTargetDevice != null) {
338                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
339                                broadcastConnectionState(mTargetDevice,
340                                                         BluetoothProfile.STATE_DISCONNECTED,
341                                                         BluetoothProfile.STATE_CONNECTING);
342                                synchronized (A2dpStateMachine.this) {
343                                    mTargetDevice = null;
344                                    transitionTo(mDisconnected);
345                                }
346                            }
347                        } else {
348                            synchronized (A2dpStateMachine.this) {
349                                mIncomingDevice = null;
350                                transitionTo(mDisconnected);
351                            }
352                        }
353                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
354                        // outgoing connection failed
355                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
356                                                 BluetoothProfile.STATE_CONNECTING);
357                        synchronized (A2dpStateMachine.this) {
358                            mTargetDevice = null;
359                            transitionTo(mDisconnected);
360                        }
361                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
362                        broadcastConnectionState(mIncomingDevice,
363                                                 BluetoothProfile.STATE_DISCONNECTED,
364                                                 BluetoothProfile.STATE_CONNECTING);
365                        synchronized (A2dpStateMachine.this) {
366                            mIncomingDevice = null;
367                            transitionTo(mDisconnected);
368                        }
369                    } else {
370                        loge("Unknown device Disconnected: " + device);
371                    }
372                    break;
373            case CONNECTION_STATE_CONNECTED:
374                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
375                    // disconnection failed
376                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
377                                             BluetoothProfile.STATE_DISCONNECTING);
378                    if (mTargetDevice != null) {
379                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
380                                                 BluetoothProfile.STATE_CONNECTING);
381                    }
382                    synchronized (A2dpStateMachine.this) {
383                        mTargetDevice = null;
384                        transitionTo(mConnected);
385                    }
386                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
387                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
388                                             BluetoothProfile.STATE_CONNECTING);
389                    synchronized (A2dpStateMachine.this) {
390                        mCurrentDevice = mTargetDevice;
391                        mTargetDevice = null;
392                        transitionTo(mConnected);
393                    }
394                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
395                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
396                                             BluetoothProfile.STATE_CONNECTING);
397                    synchronized (A2dpStateMachine.this) {
398                        mCurrentDevice = mIncomingDevice;
399                        mIncomingDevice = null;
400                        transitionTo(mConnected);
401                    }
402                } else {
403                    loge("Unknown device Connected: " + device);
404                    // something is wrong here, but sync our state with stack
405                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
406                                             BluetoothProfile.STATE_DISCONNECTED);
407                    synchronized (A2dpStateMachine.this) {
408                        mCurrentDevice = device;
409                        mTargetDevice = null;
410                        mIncomingDevice = null;
411                        transitionTo(mConnected);
412                    }
413                }
414                break;
415            case CONNECTION_STATE_CONNECTING:
416                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
417                    log("current device tries to connect back");
418                    // TODO(BT) ignore or reject
419                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
420                    // The stack is connecting to target device or
421                    // there is an incoming connection from the target device at the same time
422                    // we already broadcasted the intent, doing nothing here
423                    log("Stack and target device are connecting");
424                }
425                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
426                    loge("Another connecting event on the incoming device");
427                } else {
428                    // We get an incoming connecting request while Pending
429                    // TODO(BT) is stack handing this case? let's ignore it for now
430                    log("Incoming connection while pending, ignore");
431                }
432                break;
433            case CONNECTION_STATE_DISCONNECTING:
434                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
435                    // we already broadcasted the intent, doing nothing here
436                    if (DBG) {
437                        log("stack is disconnecting mCurrentDevice");
438                    }
439                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
440                    loge("TargetDevice is getting disconnected");
441                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
442                    loge("IncomingDevice is getting disconnected");
443                } else {
444                    loge("Disconnecting unknow device: " + device);
445                }
446                break;
447            default:
448                loge("Incorrect state: " + state);
449                break;
450            }
451        }
452
453    }
454
455    private class Connected extends State {
456        @Override
457        public void enter() {
458            log("Enter Connected: " + getCurrentMessage().what);
459            // Upon connected, the audio starts out as stopped
460            broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
461                                BluetoothA2dp.STATE_PLAYING);
462        }
463
464        @Override
465        public boolean processMessage(Message message) {
466            log("Connected process message: " + message.what);
467            if (mCurrentDevice == null) {
468                loge("ERROR: mCurrentDevice is null in Connected");
469                return NOT_HANDLED;
470            }
471
472            boolean retValue = HANDLED;
473            switch(message.what) {
474                case CONNECT:
475                {
476                    BluetoothDevice device = (BluetoothDevice) message.obj;
477                    if (mCurrentDevice.equals(device)) {
478                        break;
479                    }
480
481                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
482                                   BluetoothProfile.STATE_DISCONNECTED);
483                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
484                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
485                                       BluetoothProfile.STATE_CONNECTING);
486                        break;
487                    }
488
489                    synchronized (A2dpStateMachine.this) {
490                        mTargetDevice = device;
491                        transitionTo(mPending);
492                    }
493                }
494                    break;
495                case DISCONNECT:
496                {
497                    BluetoothDevice device = (BluetoothDevice) message.obj;
498                    if (!mCurrentDevice.equals(device)) {
499                        break;
500                    }
501                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
502                                   BluetoothProfile.STATE_CONNECTED);
503                    if (!disconnectA2dpNative(getByteAddress(device))) {
504                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
505                                       BluetoothProfile.STATE_DISCONNECTED);
506                        break;
507                    }
508                    transitionTo(mPending);
509                }
510                    break;
511                case STACK_EVENT:
512                    StackEvent event = (StackEvent) message.obj;
513                    switch (event.type) {
514                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
515                            processConnectionEvent(event.valueInt, event.device);
516                            break;
517                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
518                            processAudioStateEvent(event.valueInt, event.device);
519                            break;
520                        default:
521                            loge("Unexpected stack event: " + event.type);
522                            break;
523                    }
524                    break;
525                default:
526                    return NOT_HANDLED;
527            }
528            return retValue;
529        }
530
531        // in Connected state
532        private void processConnectionEvent(int state, BluetoothDevice device) {
533            switch (state) {
534                case CONNECTION_STATE_DISCONNECTED:
535                    if (mCurrentDevice.equals(device)) {
536                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
537                                                 BluetoothProfile.STATE_CONNECTED);
538                        synchronized (A2dpStateMachine.this) {
539                            mCurrentDevice = null;
540                            transitionTo(mDisconnected);
541                        }
542                    } else {
543                        loge("Disconnected from unknown device: " + device);
544                    }
545                    break;
546              default:
547                  loge("Connection State Device: " + device + " bad state: " + state);
548                  break;
549            }
550        }
551        private void processAudioStateEvent(int state, BluetoothDevice device) {
552            if (!mCurrentDevice.equals(device)) {
553                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
554                                                           mCurrentDevice);
555                return;
556            }
557            switch (state) {
558                case AUDIO_STATE_STARTED:
559                    if (mPlayingA2dpDevice == null) {
560                        mPlayingA2dpDevice = device;
561                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
562                        broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
563                                            BluetoothA2dp.STATE_NOT_PLAYING);
564                    }
565                    break;
566                case AUDIO_STATE_REMOTE_SUSPEND:
567                case AUDIO_STATE_STOPPED:
568                    if (mPlayingA2dpDevice != null) {
569                        mPlayingA2dpDevice = null;
570                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
571                        broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
572                                            BluetoothA2dp.STATE_PLAYING);
573                    }
574                    break;
575                default:
576                  loge("Audio State Device: " + device + " bad state: " + state);
577                  break;
578            }
579        }
580    }
581
582    int getConnectionState(BluetoothDevice device) {
583        if (getCurrentState() == mDisconnected) {
584            return BluetoothProfile.STATE_DISCONNECTED;
585        }
586
587        synchronized (this) {
588            IState currentState = getCurrentState();
589            if (currentState == mPending) {
590                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
591                    return BluetoothProfile.STATE_CONNECTING;
592                }
593                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
594                    return BluetoothProfile.STATE_DISCONNECTING;
595                }
596                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
597                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
598                }
599                return BluetoothProfile.STATE_DISCONNECTED;
600            }
601
602            if (currentState == mConnected) {
603                if (mCurrentDevice.equals(device)) {
604                    return BluetoothProfile.STATE_CONNECTED;
605                }
606                return BluetoothProfile.STATE_DISCONNECTED;
607            } else {
608                loge("Bad currentState: " + currentState);
609                return BluetoothProfile.STATE_DISCONNECTED;
610            }
611        }
612    }
613
614    List<BluetoothDevice> getConnectedDevices() {
615        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
616        synchronized(this) {
617            if (getCurrentState() == mConnected) {
618                devices.add(mCurrentDevice);
619            }
620        }
621        return devices;
622    }
623
624    boolean isPlaying(BluetoothDevice device) {
625        synchronized(this) {
626            if (device.equals(mPlayingA2dpDevice)) {
627                return true;
628            }
629        }
630        return false;
631    }
632
633    boolean okToConnect(BluetoothDevice device) {
634        AdapterService adapterService = AdapterService.getAdapterService();
635        int priority = mService.getPriority(device);
636        boolean ret = false;
637        //check if this is an incoming connection in Quiet mode.
638        if((adapterService == null) ||
639           ((adapterService.isQuietModeEnabled() == true) &&
640           (mTargetDevice == null))){
641            ret = false;
642        }
643        // check priority and accept or reject the connection. if priority is undefined
644        // it is likely that our SDP has not completed and peer is initiating the
645        // connection. Allow this connection, provided the device is bonded
646        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
647                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
648                (device.getBondState() != BluetoothDevice.BOND_NONE))){
649            ret= true;
650        }
651        return ret;
652    }
653
654    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
655        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
656        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
657        int connectionState;
658
659        for (BluetoothDevice device : bondedDevices) {
660            ParcelUuid[] featureUuids = device.getUuids();
661            if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
662                continue;
663            }
664            connectionState = getConnectionState(device);
665            for(int i = 0; i < states.length; i++) {
666                if (connectionState == states[i]) {
667                    deviceList.add(device);
668                }
669            }
670        }
671        return deviceList;
672    }
673
674
675    // This method does not check for error conditon (newState == prevState)
676    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
677
678        int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
679                BluetoothProfile.A2DP);
680
681        mWakeLock.acquire();
682        mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
683                                                        MSG_CONNECTION_STATE_CHANGED,
684                                                        prevState,
685                                                        newState,
686                                                        device),
687                                                        delay);
688    }
689
690    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
691        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
692        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
693        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
694        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
695        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
696        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
697
698        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
699    }
700
701    private byte[] getByteAddress(BluetoothDevice device) {
702        return Utils.getBytesFromAddress(device.getAddress());
703    }
704
705    private void onConnectionStateChanged(int state, byte[] address) {
706        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
707        event.valueInt = state;
708        event.device = getDevice(address);
709        sendMessage(STACK_EVENT, event);
710    }
711
712    private void onAudioStateChanged(int state, byte[] address) {
713        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
714        event.valueInt = state;
715        event.device = getDevice(address);
716        sendMessage(STACK_EVENT, event);
717    }
718    private BluetoothDevice getDevice(byte[] address) {
719        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
720    }
721
722    private class StackEvent {
723        int type = EVENT_TYPE_NONE;
724        int valueInt = 0;
725        BluetoothDevice device = null;
726
727        private StackEvent(int type) {
728            this.type = type;
729        }
730    }
731    /** Handles A2DP connection state change intent broadcasts. */
732    private class IntentBroadcastHandler extends Handler {
733
734        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
735            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
736            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
737            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
738            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
739            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
740            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
741            log("Connection state " + device + ": " + prevState + "->" + state);
742            mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.A2DP, state, prevState);
743        }
744
745        @Override
746        public void handleMessage(Message msg) {
747            switch (msg.what) {
748                case MSG_CONNECTION_STATE_CHANGED:
749                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
750                    mWakeLock.release();
751                    break;
752            }
753        }
754    }
755
756
757    // Event types for STACK_EVENT message
758    final private static int EVENT_TYPE_NONE = 0;
759    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
760    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
761
762   // Do not modify without updating the HAL bt_av.h files.
763
764    // match up with btav_connection_state_t enum of bt_av.h
765    final static int CONNECTION_STATE_DISCONNECTED = 0;
766    final static int CONNECTION_STATE_CONNECTING = 1;
767    final static int CONNECTION_STATE_CONNECTED = 2;
768    final static int CONNECTION_STATE_DISCONNECTING = 3;
769
770    // match up with btav_audio_state_t enum of bt_av.h
771    final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
772    final static int AUDIO_STATE_STOPPED = 1;
773    final static int AUDIO_STATE_STARTED = 2;
774
775    private native static void classInitNative();
776    private native void initNative();
777    private native void cleanupNative();
778    private native boolean connectA2dpNative(byte[] address);
779    private native boolean disconnectA2dpNative(byte[] address);
780}
781