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