A2dpStateMachine.java revision 33b6b7d2b08dcd7ced1809e2ac653c5e8c2514af
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.BluetoothCodecConfig;
34import android.bluetooth.BluetoothDevice;
35import android.bluetooth.BluetoothProfile;
36import android.bluetooth.BluetoothUuid;
37import android.content.Context;
38import android.content.Intent;
39import android.media.AudioManager;
40import android.os.Handler;
41import android.os.Message;
42import android.os.ParcelUuid;
43import android.os.PowerManager;
44import android.os.PowerManager.WakeLock;
45import android.util.Log;
46
47import com.android.bluetooth.Utils;
48import com.android.bluetooth.btservice.AdapterService;
49import com.android.bluetooth.btservice.ProfileService;
50import com.android.internal.util.IState;
51import com.android.internal.util.State;
52import com.android.internal.util.StateMachine;
53
54import java.util.ArrayList;
55import java.util.List;
56import java.util.Set;
57
58final class A2dpStateMachine extends StateMachine {
59    private static final boolean DBG = false;
60
61    static final int CONNECT = 1;
62    static final int DISCONNECT = 2;
63    private static final int STACK_EVENT = 101;
64    private static final int CONNECT_TIMEOUT = 201;
65
66    private Disconnected mDisconnected;
67    private Pending mPending;
68    private Connected mConnected;
69
70    private A2dpService mService;
71    private Context mContext;
72    private BluetoothAdapter mAdapter;
73    private final AudioManager mAudioManager;
74    private IntentBroadcastHandler mIntentBroadcastHandler;
75    private final WakeLock mWakeLock;
76
77    private static final int MSG_CONNECTION_STATE_CHANGED = 0;
78
79    // mCurrentDevice is the device connected before the state changes
80    // mTargetDevice is the device to be connected
81    // mIncomingDevice is the device connecting to us, valid only in Pending state
82    //                when mIncomingDevice is not null, both mCurrentDevice
83    //                  and mTargetDevice are null
84    //                when either mCurrentDevice or mTargetDevice is not null,
85    //                  mIncomingDevice is null
86    // Stable states
87    //   No connection, Disconnected state
88    //                  both mCurrentDevice and mTargetDevice are null
89    //   Connected, Connected state
90    //              mCurrentDevice is not null, mTargetDevice is null
91    // Interim states
92    //   Connecting to a device, Pending
93    //                           mCurrentDevice is null, mTargetDevice is not null
94    //   Disconnecting device, Connecting to new device
95    //     Pending
96    //     Both mCurrentDevice and mTargetDevice are not null
97    //   Disconnecting device Pending
98    //                        mCurrentDevice is not null, mTargetDevice is null
99    //   Incoming connections Pending
100    //                        Both mCurrentDevice and mTargetDevice are null
101    private BluetoothDevice mCurrentDevice = null;
102    private BluetoothDevice mTargetDevice = null;
103    private BluetoothDevice mIncomingDevice = null;
104    private BluetoothDevice mPlayingA2dpDevice = null;
105
106    private BluetoothCodecConfig mCodecConfig = null;
107
108    static {
109        classInitNative();
110    }
111
112    private A2dpStateMachine(A2dpService svc, Context context) {
113        super("A2dpStateMachine");
114        mService = svc;
115        mContext = context;
116        mAdapter = BluetoothAdapter.getDefaultAdapter();
117
118        initNative();
119
120        mDisconnected = new Disconnected();
121        mPending = new Pending();
122        mConnected = new Connected();
123
124        addState(mDisconnected);
125        addState(mPending);
126        addState(mConnected);
127
128        setInitialState(mDisconnected);
129
130        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
131        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
132
133        mIntentBroadcastHandler = new IntentBroadcastHandler();
134
135        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
136    }
137
138    static A2dpStateMachine make(A2dpService svc, Context context) {
139        Log.d("A2dpStateMachine", "make");
140        A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
141        a2dpSm.start();
142        return a2dpSm;
143    }
144
145    public void doQuit() {
146        if ((mTargetDevice != null) &&
147            (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) {
148            log("doQuit()- Move A2DP State to DISCONNECTED");
149            broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
150                                     BluetoothProfile.STATE_CONNECTING);
151        }
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 A2dp 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                        // check if there is some incoming connection request
362                        if (mIncomingDevice != null) {
363                            logi("disconnect for outgoing in pending state");
364                            synchronized (A2dpStateMachine.this) {
365                                mTargetDevice = null;
366                            }
367                            break;
368                        }
369                        synchronized (A2dpStateMachine.this) {
370                            mTargetDevice = null;
371                            transitionTo(mDisconnected);
372                        }
373                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
374                        broadcastConnectionState(mIncomingDevice,
375                                                 BluetoothProfile.STATE_DISCONNECTED,
376                                                 BluetoothProfile.STATE_CONNECTING);
377                        synchronized (A2dpStateMachine.this) {
378                            mIncomingDevice = null;
379                            transitionTo(mDisconnected);
380                        }
381                    } else {
382                        loge("Unknown device Disconnected: " + device);
383                    }
384                    break;
385            case CONNECTION_STATE_CONNECTED:
386                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
387                    // disconnection failed
388                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
389                                             BluetoothProfile.STATE_DISCONNECTING);
390                    if (mTargetDevice != null) {
391                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
392                                                 BluetoothProfile.STATE_CONNECTING);
393                    }
394                    synchronized (A2dpStateMachine.this) {
395                        mTargetDevice = null;
396                        transitionTo(mConnected);
397                    }
398                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
399                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
400                                             BluetoothProfile.STATE_CONNECTING);
401                    synchronized (A2dpStateMachine.this) {
402                        mCurrentDevice = mTargetDevice;
403                        mTargetDevice = null;
404                        transitionTo(mConnected);
405                    }
406                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
407                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
408                                             BluetoothProfile.STATE_CONNECTING);
409                    // check for a2dp connection allowed for this device in race condition
410                    if (okToConnect(mIncomingDevice)) {
411                        logi("Ready to connect incoming Connection from pending state");
412                        synchronized (A2dpStateMachine.this) {
413                            mCurrentDevice = mIncomingDevice;
414                            mIncomingDevice = null;
415                            transitionTo(mConnected);
416                        }
417                    } else {
418                        // A2dp connection unchecked for this device
419                        loge("Incoming A2DP rejected from pending state");
420                        disconnectA2dpNative(getByteAddress(device));
421                    }
422                } else {
423                    loge("Unknown device Connected: " + device);
424                    // something is wrong here, but sync our state with stack
425                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
426                                             BluetoothProfile.STATE_DISCONNECTED);
427                    synchronized (A2dpStateMachine.this) {
428                        mCurrentDevice = device;
429                        mTargetDevice = null;
430                        mIncomingDevice = null;
431                        transitionTo(mConnected);
432                    }
433                }
434                break;
435            case CONNECTION_STATE_CONNECTING:
436                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
437                    log("current device tries to connect back");
438                    // TODO(BT) ignore or reject
439                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
440                    // The stack is connecting to target device or
441                    // there is an incoming connection from the target device at the same time
442                    // we already broadcasted the intent, doing nothing here
443                    log("Stack and target device are connecting");
444                }
445                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
446                    loge("Another connecting event on the incoming device");
447                } else {
448                    // We get an incoming connecting request while Pending
449                    // TODO(BT) is stack handing this case? let's ignore it for now
450                    log("Incoming connection while pending, accept it");
451                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
452                                             BluetoothProfile.STATE_DISCONNECTED);
453                    mIncomingDevice = device;
454                }
455                break;
456            case CONNECTION_STATE_DISCONNECTING:
457                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
458                    // we already broadcasted the intent, doing nothing here
459                    if (DBG) {
460                        log("stack is disconnecting mCurrentDevice");
461                    }
462                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
463                    loge("TargetDevice is getting disconnected");
464                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
465                    loge("IncomingDevice is getting disconnected");
466                } else {
467                    loge("Disconnecting unknow device: " + device);
468                }
469                break;
470            default:
471                loge("Incorrect state: " + state);
472                break;
473            }
474        }
475
476    }
477
478    private class Connected extends State {
479        @Override
480        public void enter() {
481            // Remove pending connection attempts that were deferred during the pending
482            // state. This is to prevent auto connect attempts from disconnecting
483            // devices that previously successfully connected.
484            // TODO: This needs to check for multiple A2DP connections, once supported...
485            removeDeferredMessages(CONNECT);
486
487            log("Enter Connected: " + getCurrentMessage().what);
488            // Upon connected, the audio starts out as stopped
489            broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
490                                BluetoothA2dp.STATE_PLAYING);
491        }
492
493        @Override
494        public boolean processMessage(Message message) {
495            log("Connected process message: " + message.what);
496            if (mCurrentDevice == null) {
497                loge("ERROR: mCurrentDevice is null in Connected");
498                return NOT_HANDLED;
499            }
500
501            boolean retValue = HANDLED;
502            switch(message.what) {
503                case CONNECT:
504                {
505                    BluetoothDevice device = (BluetoothDevice) message.obj;
506                    if (mCurrentDevice.equals(device)) {
507                        break;
508                    }
509
510                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
511                                   BluetoothProfile.STATE_DISCONNECTED);
512                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
513                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
514                                       BluetoothProfile.STATE_CONNECTING);
515                        break;
516                    }
517
518                    synchronized (A2dpStateMachine.this) {
519                        mTargetDevice = device;
520                        transitionTo(mPending);
521                    }
522                }
523                    break;
524                case DISCONNECT:
525                {
526                    BluetoothDevice device = (BluetoothDevice) message.obj;
527                    if (!mCurrentDevice.equals(device)) {
528                        break;
529                    }
530                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
531                                   BluetoothProfile.STATE_CONNECTED);
532                    if (!disconnectA2dpNative(getByteAddress(device))) {
533                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
534                                       BluetoothProfile.STATE_DISCONNECTED);
535                        break;
536                    }
537                    transitionTo(mPending);
538                }
539                    break;
540                case STACK_EVENT:
541                    StackEvent event = (StackEvent) message.obj;
542                    switch (event.type) {
543                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
544                            processConnectionEvent(event.valueInt, event.device);
545                            break;
546                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
547                            processAudioStateEvent(event.valueInt, event.device);
548                            break;
549                        default:
550                            loge("Unexpected stack event: " + event.type);
551                            break;
552                    }
553                    break;
554                default:
555                    return NOT_HANDLED;
556            }
557            return retValue;
558        }
559
560        // in Connected state
561        private void processConnectionEvent(int state, BluetoothDevice device) {
562            switch (state) {
563                case CONNECTION_STATE_DISCONNECTED:
564                    if (mCurrentDevice.equals(device)) {
565                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
566                                                 BluetoothProfile.STATE_CONNECTED);
567                        synchronized (A2dpStateMachine.this) {
568                            mCurrentDevice = null;
569                            transitionTo(mDisconnected);
570                        }
571                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
572                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
573                                                 BluetoothProfile.STATE_CONNECTING);
574                        synchronized (A2dpStateMachine.this) {
575                            mTargetDevice = null;
576                        }
577                        logi("Disconnected from mTargetDevice in connected state device: " + device);
578                    } else {
579                        loge("Disconnected from unknown device: " + device);
580                    }
581                    break;
582              default:
583                  loge("Connection State Device: " + device + " bad state: " + state);
584                  break;
585            }
586        }
587        private void processAudioStateEvent(int state, BluetoothDevice device) {
588            if (!mCurrentDevice.equals(device)) {
589                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
590                                                           mCurrentDevice);
591                return;
592            }
593            switch (state) {
594                case AUDIO_STATE_STARTED:
595                    if (mPlayingA2dpDevice == null) {
596                        mPlayingA2dpDevice = device;
597                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
598                        broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
599                                            BluetoothA2dp.STATE_NOT_PLAYING);
600                    }
601                    break;
602                case AUDIO_STATE_REMOTE_SUSPEND:
603                case AUDIO_STATE_STOPPED:
604                    if (mPlayingA2dpDevice != null) {
605                        mPlayingA2dpDevice = null;
606                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
607                        broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
608                                            BluetoothA2dp.STATE_PLAYING);
609                    }
610                    break;
611                default:
612                  loge("Audio State Device: " + device + " bad state: " + state);
613                  break;
614            }
615        }
616    }
617
618    int getConnectionState(BluetoothDevice device) {
619        if (getCurrentState() == mDisconnected) {
620            return BluetoothProfile.STATE_DISCONNECTED;
621        }
622
623        synchronized (this) {
624            IState currentState = getCurrentState();
625            if (currentState == mPending) {
626                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
627                    return BluetoothProfile.STATE_CONNECTING;
628                }
629                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
630                    return BluetoothProfile.STATE_DISCONNECTING;
631                }
632                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
633                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
634                }
635                return BluetoothProfile.STATE_DISCONNECTED;
636            }
637
638            if (currentState == mConnected) {
639                if (mCurrentDevice.equals(device)) {
640                    return BluetoothProfile.STATE_CONNECTED;
641                }
642                return BluetoothProfile.STATE_DISCONNECTED;
643            } else {
644                loge("Bad currentState: " + currentState);
645                return BluetoothProfile.STATE_DISCONNECTED;
646            }
647        }
648    }
649
650    List<BluetoothDevice> getConnectedDevices() {
651        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
652        synchronized (this) {
653            if (getCurrentState() == mConnected) {
654                devices.add(mCurrentDevice);
655            }
656        }
657        return devices;
658    }
659
660    boolean isPlaying(BluetoothDevice device) {
661        synchronized (this) {
662            if (device.equals(mPlayingA2dpDevice)) {
663                return true;
664            }
665        }
666        return false;
667    }
668
669    BluetoothCodecConfig getCodecConfig() {
670        synchronized (this) {
671            return mCodecConfig;
672        }
673    }
674
675    private void onCodecConfigChanged(int codecType, int codecPriority, int sampleRate,
676            int bitsPerSample, int channelMode, long codecSpecific1, long codecSpecific2,
677            long codecSpecific3, long codecSpecific4) {
678        BluetoothCodecConfig prevCodecConfig;
679        BluetoothCodecConfig newCodecConfig = new BluetoothCodecConfig(codecType, codecPriority,
680                sampleRate, bitsPerSample, channelMode, codecSpecific1, codecSpecific2,
681                codecSpecific3, codecSpecific4);
682        synchronized (this) {
683            prevCodecConfig = mCodecConfig;
684            mCodecConfig = newCodecConfig;
685        }
686
687        Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
688        intent.putExtra(BluetoothCodecConfig.EXTRA_CODEC_CONFIG, newCodecConfig);
689        intent.putExtra(BluetoothCodecConfig.EXTRA_PREVIOUS_CODEC_CONFIG, prevCodecConfig);
690        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
691
692        log("A2DP Codec Config : " + prevCodecConfig + "->" + newCodecConfig);
693
694        // Inform the Audio Service about the codec configuration change,
695        // so the Audio Service can reset accordingly the audio feeding
696        // parameters in the Audio HAL to the Bluetooth stack.
697        if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig) && (mCurrentDevice != null)
698                && (getCurrentState() == mConnected)) {
699            // Add the device only if it is currently connected
700            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mCurrentDevice);
701            mAudioManager.handleBluetoothA2dpDeviceConfigChange(mCurrentDevice);
702        }
703        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
704    }
705
706    void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
707        setCodecConfigPreferenceNative(codecConfig.getCodecType(), codecConfig.getCodecPriority(),
708                codecConfig.getSampleRate(), codecConfig.getBitsPerSample(),
709                codecConfig.getChannelMode(), codecConfig.getCodecSpecific1(),
710                codecConfig.getCodecSpecific2(), codecConfig.getCodecSpecific3(),
711                codecConfig.getCodecSpecific4());
712    }
713
714    boolean okToConnect(BluetoothDevice device) {
715        AdapterService adapterService = AdapterService.getAdapterService();
716        int priority = mService.getPriority(device);
717        boolean ret = false;
718        //check if this is an incoming connection in Quiet mode.
719        if((adapterService == null) ||
720           ((adapterService.isQuietModeEnabled() == true) &&
721           (mTargetDevice == null))){
722            ret = false;
723        }
724        // check priority and accept or reject the connection. if priority is undefined
725        // it is likely that our SDP has not completed and peer is initiating the
726        // connection. Allow this connection, provided the device is bonded
727        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
728                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
729                (device.getBondState() != BluetoothDevice.BOND_NONE))){
730            ret= true;
731        }
732        return ret;
733    }
734
735    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
736        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
737        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
738        int connectionState;
739
740        for (BluetoothDevice device : bondedDevices) {
741            ParcelUuid[] featureUuids = device.getUuids();
742            if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
743                continue;
744            }
745            connectionState = getConnectionState(device);
746            for(int i = 0; i < states.length; i++) {
747                if (connectionState == states[i]) {
748                    deviceList.add(device);
749                }
750            }
751        }
752        return deviceList;
753    }
754
755
756    // This method does not check for error conditon (newState == prevState)
757    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
758
759        int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
760                BluetoothProfile.A2DP);
761
762        mWakeLock.acquire();
763        mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
764                                                        MSG_CONNECTION_STATE_CHANGED,
765                                                        prevState,
766                                                        newState,
767                                                        device),
768                                                        delay);
769    }
770
771    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
772        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
773        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
774        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
775        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
776        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
777        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
778
779        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
780    }
781
782    private byte[] getByteAddress(BluetoothDevice device) {
783        return Utils.getBytesFromAddress(device.getAddress());
784    }
785
786    private void onConnectionStateChanged(int state, byte[] address) {
787        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
788        event.valueInt = state;
789        event.device = getDevice(address);
790        sendMessage(STACK_EVENT, event);
791    }
792
793    private void onAudioStateChanged(int state, byte[] address) {
794        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
795        event.valueInt = state;
796        event.device = getDevice(address);
797        sendMessage(STACK_EVENT, event);
798    }
799    private BluetoothDevice getDevice(byte[] address) {
800        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
801    }
802
803    private class StackEvent {
804        int type = EVENT_TYPE_NONE;
805        int valueInt = 0;
806        BluetoothDevice device = null;
807
808        private StackEvent(int type) {
809            this.type = type;
810        }
811    }
812    /** Handles A2DP connection state change intent broadcasts. */
813    private class IntentBroadcastHandler extends Handler {
814
815        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
816            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
817            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
818            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
819            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
820            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
821            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
822            log("Connection state " + device + ": " + prevState + "->" + state);
823            mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.A2DP, state, prevState);
824        }
825
826        @Override
827        public void handleMessage(Message msg) {
828            switch (msg.what) {
829                case MSG_CONNECTION_STATE_CHANGED:
830                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
831                    mWakeLock.release();
832                    break;
833            }
834        }
835    }
836
837    public void dump(StringBuilder sb) {
838        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
839        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
840        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
841        ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
842        ProfileService.println(sb, "StateMachine: " + this.toString());
843    }
844
845    // Event types for STACK_EVENT message
846    final private static int EVENT_TYPE_NONE = 0;
847    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
848    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
849
850   // Do not modify without updating the HAL bt_av.h files.
851
852    // match up with btav_connection_state_t enum of bt_av.h
853    final static int CONNECTION_STATE_DISCONNECTED = 0;
854    final static int CONNECTION_STATE_CONNECTING = 1;
855    final static int CONNECTION_STATE_CONNECTED = 2;
856    final static int CONNECTION_STATE_DISCONNECTING = 3;
857
858    // match up with btav_audio_state_t enum of bt_av.h
859    final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
860    final static int AUDIO_STATE_STOPPED = 1;
861    final static int AUDIO_STATE_STARTED = 2;
862
863    private native static void classInitNative();
864    private native void initNative();
865    private native void cleanupNative();
866    private native boolean connectA2dpNative(byte[] address);
867    private native boolean disconnectA2dpNative(byte[] address);
868    private native boolean setCodecConfigPreferenceNative(int codecType, int codecPriority,
869            int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1,
870            long codecSpecific2, long codecSpecific3, long codecSpecific4);
871}
872