A2dpStateMachine.java revision 8bc413b0b749ea9df59e858493273e05087fe887
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.BluetoothCodecStatus;
35import android.bluetooth.BluetoothDevice;
36import android.bluetooth.BluetoothProfile;
37import android.bluetooth.BluetoothUuid;
38import android.content.Context;
39import android.content.Intent;
40import android.content.res.Resources;
41import android.content.res.Resources.NotFoundException;
42import android.media.AudioManager;
43import android.os.Handler;
44import android.os.Message;
45import android.os.ParcelUuid;
46import android.os.PowerManager;
47import android.os.PowerManager.WakeLock;
48import android.util.Log;
49
50import com.android.bluetooth.R;
51import com.android.bluetooth.Utils;
52import com.android.bluetooth.btservice.AdapterService;
53import com.android.bluetooth.btservice.ProfileService;
54import com.android.internal.util.IState;
55import com.android.internal.util.State;
56import com.android.internal.util.StateMachine;
57
58import java.util.ArrayList;
59import java.util.List;
60import java.util.Set;
61
62final class A2dpStateMachine extends StateMachine {
63    private static final boolean DBG = false;
64    private static final String TAG = "A2dpStateMachine";
65
66    static final int CONNECT = 1;
67    static final int DISCONNECT = 2;
68    private static final int STACK_EVENT = 101;
69    private static final int CONNECT_TIMEOUT = 201;
70
71    private Disconnected mDisconnected;
72    private Pending mPending;
73    private Connected mConnected;
74
75    private A2dpService mService;
76    private Context mContext;
77    private BluetoothAdapter mAdapter;
78    private final AudioManager mAudioManager;
79    private IntentBroadcastHandler mIntentBroadcastHandler;
80    private final WakeLock mWakeLock;
81    private BluetoothCodecConfig[] mCodecConfigPriorities;
82
83    private static final int MSG_CONNECTION_STATE_CHANGED = 0;
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    private BluetoothCodecStatus mCodecStatus = null;
113    private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
114    private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
115    private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
116    private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
117    private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
118
119    static {
120        classInitNative();
121    }
122
123    private A2dpStateMachine(A2dpService svc, Context context) {
124        super("A2dpStateMachine");
125        mService = svc;
126        mContext = context;
127        mAdapter = BluetoothAdapter.getDefaultAdapter();
128        mCodecConfigPriorities = assignCodecConfigPriorities();
129
130        initNative(mCodecConfigPriorities);
131
132        mDisconnected = new Disconnected();
133        mPending = new Pending();
134        mConnected = new Connected();
135
136        addState(mDisconnected);
137        addState(mPending);
138        addState(mConnected);
139
140        setInitialState(mDisconnected);
141
142        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
143        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
144
145        mIntentBroadcastHandler = new IntentBroadcastHandler();
146
147        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
148    }
149
150    static A2dpStateMachine make(A2dpService svc, Context context) {
151        Log.d(TAG, "make");
152        A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
153        a2dpSm.start();
154        return a2dpSm;
155    }
156
157    // Assign the A2DP Source codec config priorities
158    private BluetoothCodecConfig[] assignCodecConfigPriorities() {
159        Resources resources = mContext.getResources();
160        if (resources == null) {
161            return null;
162        }
163
164        int value;
165        try {
166            value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
167        } catch (NotFoundException e) {
168            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
169        }
170        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
171                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
172            mA2dpSourceCodecPrioritySbc = value;
173        }
174
175        try {
176            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac);
177        } catch (NotFoundException e) {
178            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
179        }
180        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
181                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
182            mA2dpSourceCodecPriorityAac = value;
183        }
184
185        try {
186            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx);
187        } catch (NotFoundException e) {
188            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
189        }
190        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
191                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
192            mA2dpSourceCodecPriorityAptx = value;
193        }
194
195        try {
196            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
197        } catch (NotFoundException e) {
198            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
199        }
200        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
201                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
202            mA2dpSourceCodecPriorityAptxHd = value;
203        }
204
205        try {
206            value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
207        } catch (NotFoundException e) {
208            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
209        }
210        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
211                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
212            mA2dpSourceCodecPriorityLdac = value;
213        }
214
215        BluetoothCodecConfig codecConfig;
216        BluetoothCodecConfig[] codecConfigArray =
217                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
218        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
219                mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
220                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
221                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
222                0 /* codecSpecific4 */);
223        codecConfigArray[0] = codecConfig;
224        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
225                mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
226                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
227                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
228                0 /* codecSpecific4 */);
229        codecConfigArray[1] = codecConfig;
230        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
231                mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
232                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
233                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
234                0 /* codecSpecific4 */);
235        codecConfigArray[2] = codecConfig;
236        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
237                mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
238                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
239                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
240                0 /* codecSpecific4 */);
241        codecConfigArray[3] = codecConfig;
242        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
243                mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
244                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
245                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
246                0 /* codecSpecific4 */);
247        codecConfigArray[4] = codecConfig;
248
249        return codecConfigArray;
250    }
251
252    public void doQuit() {
253        quitNow();
254    }
255
256    public void cleanup() {
257        cleanupNative();
258    }
259
260        private class Disconnected extends State {
261        @Override
262        public void enter() {
263            log("Enter Disconnected: " + getCurrentMessage().what);
264            if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
265                loge("ERROR: enter() inconsistent state in Disconnected: current = "
266                        + mCurrentDevice + " target = " + mTargetDevice + " incoming = "
267                        + mIncomingDevice);
268            }
269            // Remove Timeout msg when moved to stable state
270            removeMessages(CONNECT_TIMEOUT);
271        }
272
273        @Override
274        public boolean processMessage(Message message) {
275            log("Disconnected process message: " + message.what);
276            if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
277                loge("ERROR: not null state in Disconnected: current = " + mCurrentDevice
278                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
279                mCurrentDevice = null;
280                mTargetDevice = null;
281                mIncomingDevice = null;
282            }
283
284            boolean retValue = HANDLED;
285            switch(message.what) {
286                case CONNECT:
287                    BluetoothDevice device = (BluetoothDevice) message.obj;
288                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
289                                   BluetoothProfile.STATE_DISCONNECTED);
290
291                    if (!connectA2dpNative(getByteAddress(device)) ) {
292                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
293                                       BluetoothProfile.STATE_CONNECTING);
294                        break;
295                    }
296
297                    synchronized (A2dpStateMachine.this) {
298                        mTargetDevice = device;
299                        transitionTo(mPending);
300                    }
301                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
302                    //          sends back events consistently
303                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
304                    break;
305                case DISCONNECT:
306                    // ignore
307                    break;
308                case STACK_EVENT:
309                    StackEvent event = (StackEvent) message.obj;
310                    switch (event.type) {
311                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
312                            processConnectionEvent(event.valueInt, event.device);
313                            break;
314                        default:
315                            loge("Unexpected stack event: " + event.type);
316                            break;
317                    }
318                    break;
319                default:
320                    return NOT_HANDLED;
321            }
322            return retValue;
323        }
324
325        @Override
326        public void exit() {
327            log("Exit Disconnected: " + getCurrentMessage().what);
328        }
329
330        // in Disconnected state
331        private void processConnectionEvent(int state, BluetoothDevice device) {
332            switch (state) {
333            case CONNECTION_STATE_DISCONNECTED:
334                logw("Ignore HF DISCONNECTED event, device: " + device);
335                break;
336            case CONNECTION_STATE_CONNECTING:
337                if (okToConnect(device)){
338                    logi("Incoming A2DP accepted");
339                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
340                                             BluetoothProfile.STATE_DISCONNECTED);
341                    synchronized (A2dpStateMachine.this) {
342                        mIncomingDevice = device;
343                        transitionTo(mPending);
344                    }
345                } else {
346                    //reject the connection and stay in Disconnected state itself
347                    logi("Incoming A2DP rejected");
348                    disconnectA2dpNative(getByteAddress(device));
349                }
350                break;
351            case CONNECTION_STATE_CONNECTED:
352                logw("A2DP Connected from Disconnected state");
353                if (okToConnect(device)){
354                    logi("Incoming A2DP accepted");
355                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
356                                             BluetoothProfile.STATE_DISCONNECTED);
357                    synchronized (A2dpStateMachine.this) {
358                        mCurrentDevice = device;
359                        transitionTo(mConnected);
360                    }
361                } else {
362                    //reject the connection and stay in Disconnected state itself
363                    logi("Incoming A2DP rejected");
364                    disconnectA2dpNative(getByteAddress(device));
365                }
366                break;
367            case CONNECTION_STATE_DISCONNECTING:
368                logw("Ignore A2dp DISCONNECTING event, device: " + device);
369                break;
370            default:
371                loge("Incorrect state: " + state);
372                break;
373            }
374        }
375    }
376
377    private class Pending extends State {
378        @Override
379        public void enter() {
380            log("Enter Pending: " + getCurrentMessage().what);
381            if (mTargetDevice != null && mIncomingDevice != null) {
382                loge("ERROR: enter() inconsistent state in Pending: current = " + mCurrentDevice
383                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
384            }
385        }
386
387        @Override
388        public boolean processMessage(Message message) {
389            log("Pending process message: " + message.what);
390
391            boolean retValue = HANDLED;
392            switch(message.what) {
393                case CONNECT:
394                    deferMessage(message);
395                    break;
396                case CONNECT_TIMEOUT:
397                    disconnectA2dpNative(getByteAddress(mTargetDevice));
398                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
399                                             getByteAddress(mTargetDevice));
400                    break;
401                case DISCONNECT:
402                    BluetoothDevice device = (BluetoothDevice) message.obj;
403                    if (mCurrentDevice != null && mTargetDevice != null &&
404                        mTargetDevice.equals(device) ) {
405                        // cancel connection to the mTargetDevice
406                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
407                                       BluetoothProfile.STATE_CONNECTING);
408                        synchronized (A2dpStateMachine.this) {
409                            mTargetDevice = null;
410                        }
411                    } else {
412                        deferMessage(message);
413                    }
414                    break;
415                case STACK_EVENT:
416                    StackEvent event = (StackEvent) message.obj;
417                    switch (event.type) {
418                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
419                            processConnectionEvent(event.valueInt, event.device);
420                            break;
421                        default:
422                            loge("Unexpected stack event: " + event.type);
423                            break;
424                    }
425                    break;
426                default:
427                    return NOT_HANDLED;
428            }
429            return retValue;
430        }
431
432        // in Pending state
433        private void processConnectionEvent(int state, BluetoothDevice device) {
434            switch (state) {
435                case CONNECTION_STATE_DISCONNECTED:
436                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
437                        broadcastConnectionState(mCurrentDevice,
438                                                 BluetoothProfile.STATE_DISCONNECTED,
439                                                 BluetoothProfile.STATE_DISCONNECTING);
440                        synchronized (A2dpStateMachine.this) {
441                            mCurrentDevice = null;
442                        }
443
444                        if (mTargetDevice != null) {
445                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
446                                broadcastConnectionState(mTargetDevice,
447                                                         BluetoothProfile.STATE_DISCONNECTED,
448                                                         BluetoothProfile.STATE_CONNECTING);
449                                synchronized (A2dpStateMachine.this) {
450                                    mTargetDevice = null;
451                                    transitionTo(mDisconnected);
452                                }
453                            }
454                        } else {
455                            synchronized (A2dpStateMachine.this) {
456                                mIncomingDevice = null;
457                                transitionTo(mDisconnected);
458                            }
459                        }
460                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
461                        // outgoing connection failed
462                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
463                                                 BluetoothProfile.STATE_CONNECTING);
464                        // check if there is some incoming connection request
465                        if (mIncomingDevice != null) {
466                            logi("disconnect for outgoing in pending state");
467                            synchronized (A2dpStateMachine.this) {
468                                mTargetDevice = null;
469                            }
470                            break;
471                        }
472                        synchronized (A2dpStateMachine.this) {
473                            mTargetDevice = null;
474                            transitionTo(mDisconnected);
475                        }
476                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
477                        broadcastConnectionState(mIncomingDevice,
478                                                 BluetoothProfile.STATE_DISCONNECTED,
479                                                 BluetoothProfile.STATE_CONNECTING);
480                        synchronized (A2dpStateMachine.this) {
481                            mIncomingDevice = null;
482                            transitionTo(mDisconnected);
483                        }
484                    } else {
485                        loge("Unknown device Disconnected: " + device);
486                    }
487                    break;
488            case CONNECTION_STATE_CONNECTED:
489                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
490                    // disconnection failed
491                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
492                                             BluetoothProfile.STATE_DISCONNECTING);
493                    if (mTargetDevice != null) {
494                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
495                                                 BluetoothProfile.STATE_CONNECTING);
496                    }
497                    synchronized (A2dpStateMachine.this) {
498                        mTargetDevice = null;
499                        transitionTo(mConnected);
500                    }
501                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
502                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
503                                             BluetoothProfile.STATE_CONNECTING);
504                    synchronized (A2dpStateMachine.this) {
505                        mCurrentDevice = mTargetDevice;
506                        mTargetDevice = null;
507                        transitionTo(mConnected);
508                    }
509                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
510                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
511                                             BluetoothProfile.STATE_CONNECTING);
512                    // check for a2dp connection allowed for this device in race condition
513                    if (okToConnect(mIncomingDevice)) {
514                        logi("Ready to connect incoming Connection from pending state");
515                        synchronized (A2dpStateMachine.this) {
516                            mCurrentDevice = mIncomingDevice;
517                            mIncomingDevice = null;
518                            transitionTo(mConnected);
519                        }
520                    } else {
521                        // A2dp connection unchecked for this device
522                        loge("Incoming A2DP rejected from pending state");
523                        disconnectA2dpNative(getByteAddress(device));
524                    }
525                } else {
526                    loge("Unknown device Connected: " + device);
527                    // something is wrong here, but sync our state with stack
528                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
529                                             BluetoothProfile.STATE_DISCONNECTED);
530                    synchronized (A2dpStateMachine.this) {
531                        mCurrentDevice = device;
532                        mTargetDevice = null;
533                        mIncomingDevice = null;
534                        transitionTo(mConnected);
535                    }
536                }
537                break;
538            case CONNECTION_STATE_CONNECTING:
539                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
540                    log("current device tries to connect back");
541                    // TODO(BT) ignore or reject
542                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
543                    // The stack is connecting to target device or
544                    // there is an incoming connection from the target device at the same time
545                    // we already broadcasted the intent, doing nothing here
546                    log("Stack and target device are connecting");
547                }
548                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
549                    loge("Another connecting event on the incoming device");
550                } else {
551                    // We get an incoming connecting request while Pending
552                    // TODO(BT) is stack handing this case? let's ignore it for now
553                    log("Incoming connection while pending, accept it");
554                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
555                                             BluetoothProfile.STATE_DISCONNECTED);
556                    mIncomingDevice = device;
557                }
558                break;
559            case CONNECTION_STATE_DISCONNECTING:
560                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
561                    // we already broadcasted the intent, doing nothing here
562                    if (DBG) {
563                        log("stack is disconnecting mCurrentDevice");
564                    }
565                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
566                    loge("TargetDevice is getting disconnected");
567                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
568                    loge("IncomingDevice is getting disconnected");
569                } else {
570                    loge("Disconnecting unknow device: " + device);
571                }
572                break;
573            default:
574                loge("Incorrect state: " + state);
575                break;
576            }
577        }
578
579    }
580
581    private class Connected extends State {
582        @Override
583        public void enter() {
584            // Remove pending connection attempts that were deferred during the pending
585            // state. This is to prevent auto connect attempts from disconnecting
586            // devices that previously successfully connected.
587            // TODO: This needs to check for multiple A2DP connections, once supported...
588            removeDeferredMessages(CONNECT);
589
590            log("Enter Connected: " + getCurrentMessage().what);
591            if (mTargetDevice != null || mIncomingDevice != null) {
592                loge("ERROR: enter() inconsistent state in Connected: current = " + mCurrentDevice
593                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
594            }
595
596            // remove timeout for connected device only.
597            if (mTargetDevice == null) {
598                removeMessages(CONNECT_TIMEOUT);
599            }
600            // Upon connected, the audio starts out as stopped
601            broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
602                                BluetoothA2dp.STATE_PLAYING);
603        }
604
605        @Override
606        public boolean processMessage(Message message) {
607            log("Connected process message: " + message.what);
608            if (mCurrentDevice == null) {
609                loge("ERROR: mCurrentDevice is null in Connected");
610                return NOT_HANDLED;
611            }
612
613            boolean retValue = HANDLED;
614            switch(message.what) {
615                case CONNECT:
616                {
617                    BluetoothDevice device = (BluetoothDevice) message.obj;
618                    if (mCurrentDevice.equals(device)) {
619                        break;
620                    }
621
622                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
623                                   BluetoothProfile.STATE_DISCONNECTED);
624                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
625                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
626                                       BluetoothProfile.STATE_CONNECTING);
627                        break;
628                    } else {
629                        broadcastConnectionState(mCurrentDevice,
630                                BluetoothProfile.STATE_DISCONNECTING,
631                                BluetoothProfile.STATE_CONNECTED);
632                    }
633
634                    synchronized (A2dpStateMachine.this) {
635                        mTargetDevice = device;
636                        transitionTo(mPending);
637                    }
638                }
639                    break;
640                case DISCONNECT:
641                {
642                    BluetoothDevice device = (BluetoothDevice) message.obj;
643                    if (!mCurrentDevice.equals(device)) {
644                        break;
645                    }
646                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
647                                   BluetoothProfile.STATE_CONNECTED);
648                    if (!disconnectA2dpNative(getByteAddress(device))) {
649                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
650                                BluetoothProfile.STATE_DISCONNECTING);
651                        break;
652                    }
653                    synchronized (A2dpStateMachine.this) {
654                        transitionTo(mPending);
655                    }
656                }
657                    break;
658                case CONNECT_TIMEOUT:
659                    if (mTargetDevice == null) {
660                        loge("CONNECT_TIMEOUT received for unknown device");
661                    } else {
662                        loge("CONNECT_TIMEOUT received : connected device : " + mCurrentDevice
663                                + " : timedout device : " + mTargetDevice);
664                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
665                                BluetoothProfile.STATE_CONNECTING);
666                        mTargetDevice = null;
667                    }
668                    break;
669                case STACK_EVENT:
670                    StackEvent event = (StackEvent) message.obj;
671                    switch (event.type) {
672                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
673                            processConnectionEvent(event.valueInt, event.device);
674                            break;
675                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
676                            processAudioStateEvent(event.valueInt, event.device);
677                            break;
678                        default:
679                            loge("Unexpected stack event: " + event.type);
680                            break;
681                    }
682                    break;
683                default:
684                    return NOT_HANDLED;
685            }
686            return retValue;
687        }
688
689        // in Connected state
690        private void processConnectionEvent(int state, BluetoothDevice device) {
691            switch (state) {
692                case CONNECTION_STATE_DISCONNECTED:
693                    if (mCurrentDevice.equals(device)) {
694                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
695                                                 BluetoothProfile.STATE_CONNECTED);
696                        synchronized (A2dpStateMachine.this) {
697                            mCurrentDevice = null;
698                            transitionTo(mDisconnected);
699                        }
700                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
701                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
702                                                 BluetoothProfile.STATE_CONNECTING);
703                        synchronized (A2dpStateMachine.this) {
704                            mTargetDevice = null;
705                        }
706                        logi("Disconnected from mTargetDevice in connected state device: " + device);
707                    } else {
708                        loge("Disconnected from unknown device: " + device);
709                    }
710                    break;
711              default:
712                  loge("Connection State Device: " + device + " bad state: " + state);
713                  break;
714            }
715        }
716        private void processAudioStateEvent(int state, BluetoothDevice device) {
717            if (!mCurrentDevice.equals(device)) {
718                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
719                                                           mCurrentDevice);
720                return;
721            }
722            switch (state) {
723                case AUDIO_STATE_STARTED:
724                    if (mPlayingA2dpDevice == null) {
725                        mPlayingA2dpDevice = device;
726                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
727                        broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
728                                            BluetoothA2dp.STATE_NOT_PLAYING);
729                    }
730                    break;
731                case AUDIO_STATE_REMOTE_SUSPEND:
732                case AUDIO_STATE_STOPPED:
733                    if (mPlayingA2dpDevice != null) {
734                        mPlayingA2dpDevice = null;
735                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
736                        broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
737                                            BluetoothA2dp.STATE_PLAYING);
738                    }
739                    break;
740                default:
741                  loge("Audio State Device: " + device + " bad state: " + state);
742                  break;
743            }
744        }
745    }
746
747    int getConnectionState(BluetoothDevice device) {
748        if (getCurrentState() == mDisconnected) {
749            return BluetoothProfile.STATE_DISCONNECTED;
750        }
751
752        synchronized (this) {
753            IState currentState = getCurrentState();
754            if (currentState == mPending) {
755                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
756                    return BluetoothProfile.STATE_CONNECTING;
757                }
758                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
759                    return BluetoothProfile.STATE_DISCONNECTING;
760                }
761                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
762                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
763                }
764                return BluetoothProfile.STATE_DISCONNECTED;
765            }
766
767            if (currentState == mConnected) {
768                if (mCurrentDevice.equals(device)) {
769                    return BluetoothProfile.STATE_CONNECTED;
770                }
771                return BluetoothProfile.STATE_DISCONNECTED;
772            } else {
773                loge("Bad currentState: " + currentState);
774                return BluetoothProfile.STATE_DISCONNECTED;
775            }
776        }
777    }
778
779    List<BluetoothDevice> getConnectedDevices() {
780        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
781        synchronized (this) {
782            if (getCurrentState() == mConnected) {
783                devices.add(mCurrentDevice);
784            }
785        }
786        return devices;
787    }
788
789    boolean isPlaying(BluetoothDevice device) {
790        synchronized (this) {
791            if (device.equals(mPlayingA2dpDevice)) {
792                return true;
793            }
794        }
795        return false;
796    }
797
798    BluetoothCodecStatus getCodecStatus() {
799        synchronized (this) {
800            return mCodecStatus;
801        }
802    }
803
804    private void onCodecConfigChanged(BluetoothCodecConfig newCodecConfig,
805            BluetoothCodecConfig[] codecsLocalCapabilities,
806            BluetoothCodecConfig[] codecsSelectableCapabilities) {
807        BluetoothCodecConfig prevCodecConfig = null;
808        synchronized (this) {
809            if (mCodecStatus != null) {
810                prevCodecConfig = mCodecStatus.getCodecConfig();
811            }
812            mCodecStatus = new BluetoothCodecStatus(
813                    newCodecConfig, codecsLocalCapabilities, codecsSelectableCapabilities);
814        }
815
816        Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
817        intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus);
818        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
819
820        log("A2DP Codec Config: " + prevCodecConfig + "->" + newCodecConfig);
821        for (BluetoothCodecConfig codecConfig : codecsLocalCapabilities) {
822            log("A2DP Codec Local Capability: " + codecConfig);
823        }
824        for (BluetoothCodecConfig codecConfig : codecsSelectableCapabilities) {
825            log("A2DP Codec Selectable Capability: " + codecConfig);
826        }
827
828        // Inform the Audio Service about the codec configuration change,
829        // so the Audio Service can reset accordingly the audio feeding
830        // parameters in the Audio HAL to the Bluetooth stack.
831        if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig) && (mCurrentDevice != null)
832                && (getCurrentState() == mConnected)) {
833            // Add the device only if it is currently connected
834            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mCurrentDevice);
835            mAudioManager.handleBluetoothA2dpDeviceConfigChange(mCurrentDevice);
836        }
837        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
838    }
839
840    void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
841        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
842        codecConfigArray[0] = codecConfig;
843        setCodecConfigPreferenceNative(codecConfigArray);
844    }
845
846    void enableOptionalCodecs() {
847        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
848        if (codecConfigArray == null) {
849            return;
850        }
851
852        // Set the mandatory codec's priority to default, and remove the rest
853        for (int i = 0; i < codecConfigArray.length; i++) {
854            BluetoothCodecConfig codecConfig = codecConfigArray[i];
855            if (!codecConfig.isMandatoryCodec()) {
856                codecConfigArray[i] = null;
857            }
858        }
859
860        setCodecConfigPreferenceNative(codecConfigArray);
861    }
862
863    void disableOptionalCodecs() {
864        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
865        if (codecConfigArray == null) {
866            return;
867        }
868        // Set the mandatory codec's priority to highest, and ignore the rest
869        for (int i = 0; i < codecConfigArray.length; i++) {
870            BluetoothCodecConfig codecConfig = codecConfigArray[i];
871            if (codecConfig.isMandatoryCodec()) {
872                codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
873            } else {
874                codecConfigArray[i] = null;
875            }
876        }
877        setCodecConfigPreferenceNative(codecConfigArray);
878    }
879
880    boolean okToConnect(BluetoothDevice device) {
881        AdapterService adapterService = AdapterService.getAdapterService();
882        int priority = mService.getPriority(device);
883        boolean ret = false;
884        //check if this is an incoming connection in Quiet mode.
885        if((adapterService == null) ||
886           ((adapterService.isQuietModeEnabled()) &&
887           (mTargetDevice == null))){
888            ret = false;
889        }
890        // check priority and accept or reject the connection. if priority is undefined
891        // it is likely that our SDP has not completed and peer is initiating the
892        // connection. Allow this connection, provided the device is bonded
893        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
894                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
895                (device.getBondState() != BluetoothDevice.BOND_NONE))){
896            ret= true;
897        }
898        return ret;
899    }
900
901    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
902        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
903        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
904        int connectionState;
905
906        for (BluetoothDevice device : bondedDevices) {
907            ParcelUuid[] featureUuids = device.getUuids();
908            if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
909                continue;
910            }
911            connectionState = getConnectionState(device);
912            for(int i = 0; i < states.length; i++) {
913                if (connectionState == states[i]) {
914                    deviceList.add(device);
915                }
916            }
917        }
918        return deviceList;
919    }
920
921
922    // This method does not check for error conditon (newState == prevState)
923    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
924        mAudioManager.setBluetoothA2dpDeviceConnectionState(
925                device, newState, BluetoothProfile.A2DP);
926
927        mWakeLock.acquire();
928        mIntentBroadcastHandler.sendMessage(mIntentBroadcastHandler.obtainMessage(
929                MSG_CONNECTION_STATE_CHANGED, prevState, newState, device));
930    }
931
932    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
933        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
934        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
935        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
936        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
937        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
938        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
939
940        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
941    }
942
943    private byte[] getByteAddress(BluetoothDevice device) {
944        return Utils.getBytesFromAddress(device.getAddress());
945    }
946
947    private void onConnectionStateChanged(int state, byte[] address) {
948        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
949        event.valueInt = state;
950        event.device = getDevice(address);
951        sendMessage(STACK_EVENT, event);
952    }
953
954    private void onAudioStateChanged(int state, byte[] address) {
955        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
956        event.valueInt = state;
957        event.device = getDevice(address);
958        sendMessage(STACK_EVENT, event);
959    }
960    private BluetoothDevice getDevice(byte[] address) {
961        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
962    }
963
964    private class StackEvent {
965        public int type = EVENT_TYPE_NONE;
966        public int valueInt = 0;
967        public BluetoothDevice device = null;
968
969        private StackEvent(int type) {
970            this.type = type;
971        }
972    }
973    /** Handles A2DP connection state change intent broadcasts. */
974    private class IntentBroadcastHandler extends Handler {
975
976        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
977            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
978            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
979            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
980            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
981            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
982                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
983            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
984            log("Connection state " + device + ": " + prevState + "->" + state);
985        }
986
987        @Override
988        public void handleMessage(Message msg) {
989            switch (msg.what) {
990                case MSG_CONNECTION_STATE_CHANGED:
991                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
992                    mWakeLock.release();
993                    break;
994            }
995        }
996    }
997
998    public void dump(StringBuilder sb) {
999        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
1000        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
1001        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
1002        ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
1003        ProfileService.println(sb, "StateMachine: " + this.toString());
1004    }
1005
1006    // Event types for STACK_EVENT message
1007    private static final int EVENT_TYPE_NONE = 0;
1008    private static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
1009    private static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
1010
1011   // Do not modify without updating the HAL bt_av.h files.
1012
1013    // match up with btav_connection_state_t enum of bt_av.h
1014    static final int CONNECTION_STATE_DISCONNECTED = 0;
1015    static final int CONNECTION_STATE_CONNECTING = 1;
1016    static final int CONNECTION_STATE_CONNECTED = 2;
1017    static final int CONNECTION_STATE_DISCONNECTING = 3;
1018
1019    // match up with btav_audio_state_t enum of bt_av.h
1020    static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
1021    static final int AUDIO_STATE_STOPPED = 1;
1022    static final int AUDIO_STATE_STARTED = 2;
1023
1024    private static native void classInitNative();
1025    private native void initNative(BluetoothCodecConfig[] codecConfigPriorites);
1026    private native void cleanupNative();
1027    private native boolean connectA2dpNative(byte[] address);
1028    private native boolean disconnectA2dpNative(byte[] address);
1029    private native boolean setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray);
1030}
1031