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