A2dpStateMachine.java revision 7aec8406176f8cef0b608c22b6e631df71ccd363
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        if ((mTargetDevice != null) &&
254            (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) {
255            log("doQuit()- Move A2DP State to DISCONNECTED");
256            broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
257                                     BluetoothProfile.STATE_CONNECTING);
258        }
259        quitNow();
260    }
261
262    public void cleanup() {
263        cleanupNative();
264    }
265
266        private class Disconnected extends State {
267        @Override
268        public void enter() {
269            log("Enter Disconnected: " + getCurrentMessage().what);
270        }
271
272        @Override
273        public boolean processMessage(Message message) {
274            log("Disconnected process message: " + message.what);
275            if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
276                loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
277                return NOT_HANDLED;
278            }
279
280            boolean retValue = HANDLED;
281            switch(message.what) {
282                case CONNECT:
283                    BluetoothDevice device = (BluetoothDevice) message.obj;
284                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
285                                   BluetoothProfile.STATE_DISCONNECTED);
286
287                    if (!connectA2dpNative(getByteAddress(device)) ) {
288                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
289                                       BluetoothProfile.STATE_CONNECTING);
290                        break;
291                    }
292
293                    synchronized (A2dpStateMachine.this) {
294                        mTargetDevice = device;
295                        transitionTo(mPending);
296                    }
297                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
298                    //          sends back events consistently
299                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
300                    break;
301                case DISCONNECT:
302                    // ignore
303                    break;
304                case STACK_EVENT:
305                    StackEvent event = (StackEvent) message.obj;
306                    switch (event.type) {
307                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
308                            processConnectionEvent(event.valueInt, event.device);
309                            break;
310                        default:
311                            loge("Unexpected stack event: " + event.type);
312                            break;
313                    }
314                    break;
315                default:
316                    return NOT_HANDLED;
317            }
318            return retValue;
319        }
320
321        @Override
322        public void exit() {
323            log("Exit Disconnected: " + getCurrentMessage().what);
324        }
325
326        // in Disconnected state
327        private void processConnectionEvent(int state, BluetoothDevice device) {
328            switch (state) {
329            case CONNECTION_STATE_DISCONNECTED:
330                logw("Ignore HF DISCONNECTED event, device: " + device);
331                break;
332            case CONNECTION_STATE_CONNECTING:
333                if (okToConnect(device)){
334                    logi("Incoming A2DP accepted");
335                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
336                                             BluetoothProfile.STATE_DISCONNECTED);
337                    synchronized (A2dpStateMachine.this) {
338                        mIncomingDevice = device;
339                        transitionTo(mPending);
340                    }
341                } else {
342                    //reject the connection and stay in Disconnected state itself
343                    logi("Incoming A2DP rejected");
344                    disconnectA2dpNative(getByteAddress(device));
345                }
346                break;
347            case CONNECTION_STATE_CONNECTED:
348                logw("A2DP Connected from Disconnected state");
349                if (okToConnect(device)){
350                    logi("Incoming A2DP accepted");
351                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
352                                             BluetoothProfile.STATE_DISCONNECTED);
353                    synchronized (A2dpStateMachine.this) {
354                        mCurrentDevice = device;
355                        transitionTo(mConnected);
356                    }
357                } else {
358                    //reject the connection and stay in Disconnected state itself
359                    logi("Incoming A2DP rejected");
360                    disconnectA2dpNative(getByteAddress(device));
361                }
362                break;
363            case CONNECTION_STATE_DISCONNECTING:
364                logw("Ignore A2dp DISCONNECTING event, device: " + device);
365                break;
366            default:
367                loge("Incorrect state: " + state);
368                break;
369            }
370        }
371    }
372
373    private class Pending extends State {
374        @Override
375        public void enter() {
376            log("Enter Pending: " + getCurrentMessage().what);
377        }
378
379        @Override
380        public boolean processMessage(Message message) {
381            log("Pending process message: " + message.what);
382
383            boolean retValue = HANDLED;
384            switch(message.what) {
385                case CONNECT:
386                    deferMessage(message);
387                    break;
388                case CONNECT_TIMEOUT:
389                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
390                                             getByteAddress(mTargetDevice));
391                    break;
392                case DISCONNECT:
393                    BluetoothDevice device = (BluetoothDevice) message.obj;
394                    if (mCurrentDevice != null && mTargetDevice != null &&
395                        mTargetDevice.equals(device) ) {
396                        // cancel connection to the mTargetDevice
397                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
398                                       BluetoothProfile.STATE_CONNECTING);
399                        synchronized (A2dpStateMachine.this) {
400                            mTargetDevice = null;
401                        }
402                    } else {
403                        deferMessage(message);
404                    }
405                    break;
406                case STACK_EVENT:
407                    StackEvent event = (StackEvent) message.obj;
408                    switch (event.type) {
409                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
410                            removeMessages(CONNECT_TIMEOUT);
411                            processConnectionEvent(event.valueInt, event.device);
412                            break;
413                        default:
414                            loge("Unexpected stack event: " + event.type);
415                            break;
416                    }
417                    break;
418                default:
419                    return NOT_HANDLED;
420            }
421            return retValue;
422        }
423
424        // in Pending state
425        private void processConnectionEvent(int state, BluetoothDevice device) {
426            switch (state) {
427                case CONNECTION_STATE_DISCONNECTED:
428                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
429                        broadcastConnectionState(mCurrentDevice,
430                                                 BluetoothProfile.STATE_DISCONNECTED,
431                                                 BluetoothProfile.STATE_DISCONNECTING);
432                        synchronized (A2dpStateMachine.this) {
433                            mCurrentDevice = null;
434                        }
435
436                        if (mTargetDevice != null) {
437                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
438                                broadcastConnectionState(mTargetDevice,
439                                                         BluetoothProfile.STATE_DISCONNECTED,
440                                                         BluetoothProfile.STATE_CONNECTING);
441                                synchronized (A2dpStateMachine.this) {
442                                    mTargetDevice = null;
443                                    transitionTo(mDisconnected);
444                                }
445                            }
446                        } else {
447                            synchronized (A2dpStateMachine.this) {
448                                mIncomingDevice = null;
449                                transitionTo(mDisconnected);
450                            }
451                        }
452                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
453                        // outgoing connection failed
454                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
455                                                 BluetoothProfile.STATE_CONNECTING);
456                        // check if there is some incoming connection request
457                        if (mIncomingDevice != null) {
458                            logi("disconnect for outgoing in pending state");
459                            synchronized (A2dpStateMachine.this) {
460                                mTargetDevice = null;
461                            }
462                            break;
463                        }
464                        synchronized (A2dpStateMachine.this) {
465                            mTargetDevice = null;
466                            transitionTo(mDisconnected);
467                        }
468                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
469                        broadcastConnectionState(mIncomingDevice,
470                                                 BluetoothProfile.STATE_DISCONNECTED,
471                                                 BluetoothProfile.STATE_CONNECTING);
472                        synchronized (A2dpStateMachine.this) {
473                            mIncomingDevice = null;
474                            transitionTo(mDisconnected);
475                        }
476                    } else {
477                        loge("Unknown device Disconnected: " + device);
478                    }
479                    break;
480            case CONNECTION_STATE_CONNECTED:
481                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
482                    // disconnection failed
483                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
484                                             BluetoothProfile.STATE_DISCONNECTING);
485                    if (mTargetDevice != null) {
486                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
487                                                 BluetoothProfile.STATE_CONNECTING);
488                    }
489                    synchronized (A2dpStateMachine.this) {
490                        mTargetDevice = null;
491                        transitionTo(mConnected);
492                    }
493                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
494                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
495                                             BluetoothProfile.STATE_CONNECTING);
496                    synchronized (A2dpStateMachine.this) {
497                        mCurrentDevice = mTargetDevice;
498                        mTargetDevice = null;
499                        transitionTo(mConnected);
500                    }
501                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
502                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
503                                             BluetoothProfile.STATE_CONNECTING);
504                    // check for a2dp connection allowed for this device in race condition
505                    if (okToConnect(mIncomingDevice)) {
506                        logi("Ready to connect incoming Connection from pending state");
507                        synchronized (A2dpStateMachine.this) {
508                            mCurrentDevice = mIncomingDevice;
509                            mIncomingDevice = null;
510                            transitionTo(mConnected);
511                        }
512                    } else {
513                        // A2dp connection unchecked for this device
514                        loge("Incoming A2DP rejected from pending state");
515                        disconnectA2dpNative(getByteAddress(device));
516                    }
517                } else {
518                    loge("Unknown device Connected: " + device);
519                    // something is wrong here, but sync our state with stack
520                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
521                                             BluetoothProfile.STATE_DISCONNECTED);
522                    synchronized (A2dpStateMachine.this) {
523                        mCurrentDevice = device;
524                        mTargetDevice = null;
525                        mIncomingDevice = null;
526                        transitionTo(mConnected);
527                    }
528                }
529                break;
530            case CONNECTION_STATE_CONNECTING:
531                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
532                    log("current device tries to connect back");
533                    // TODO(BT) ignore or reject
534                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
535                    // The stack is connecting to target device or
536                    // there is an incoming connection from the target device at the same time
537                    // we already broadcasted the intent, doing nothing here
538                    log("Stack and target device are connecting");
539                }
540                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
541                    loge("Another connecting event on the incoming device");
542                } else {
543                    // We get an incoming connecting request while Pending
544                    // TODO(BT) is stack handing this case? let's ignore it for now
545                    log("Incoming connection while pending, accept it");
546                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
547                                             BluetoothProfile.STATE_DISCONNECTED);
548                    mIncomingDevice = device;
549                }
550                break;
551            case CONNECTION_STATE_DISCONNECTING:
552                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
553                    // we already broadcasted the intent, doing nothing here
554                    if (DBG) {
555                        log("stack is disconnecting mCurrentDevice");
556                    }
557                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
558                    loge("TargetDevice is getting disconnected");
559                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
560                    loge("IncomingDevice is getting disconnected");
561                } else {
562                    loge("Disconnecting unknow device: " + device);
563                }
564                break;
565            default:
566                loge("Incorrect state: " + state);
567                break;
568            }
569        }
570
571    }
572
573    private class Connected extends State {
574        @Override
575        public void enter() {
576            // Remove pending connection attempts that were deferred during the pending
577            // state. This is to prevent auto connect attempts from disconnecting
578            // devices that previously successfully connected.
579            // TODO: This needs to check for multiple A2DP connections, once supported...
580            removeDeferredMessages(CONNECT);
581
582            log("Enter Connected: " + getCurrentMessage().what);
583            // Upon connected, the audio starts out as stopped
584            broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
585                                BluetoothA2dp.STATE_PLAYING);
586        }
587
588        @Override
589        public boolean processMessage(Message message) {
590            log("Connected process message: " + message.what);
591            if (mCurrentDevice == null) {
592                loge("ERROR: mCurrentDevice is null in Connected");
593                return NOT_HANDLED;
594            }
595
596            boolean retValue = HANDLED;
597            switch(message.what) {
598                case CONNECT:
599                {
600                    BluetoothDevice device = (BluetoothDevice) message.obj;
601                    if (mCurrentDevice.equals(device)) {
602                        break;
603                    }
604
605                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
606                                   BluetoothProfile.STATE_DISCONNECTED);
607                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
608                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
609                                       BluetoothProfile.STATE_CONNECTING);
610                        break;
611                    }
612
613                    synchronized (A2dpStateMachine.this) {
614                        mTargetDevice = device;
615                        transitionTo(mPending);
616                    }
617                }
618                    break;
619                case DISCONNECT:
620                {
621                    BluetoothDevice device = (BluetoothDevice) message.obj;
622                    if (!mCurrentDevice.equals(device)) {
623                        break;
624                    }
625                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
626                                   BluetoothProfile.STATE_CONNECTED);
627                    if (!disconnectA2dpNative(getByteAddress(device))) {
628                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
629                                       BluetoothProfile.STATE_DISCONNECTED);
630                        break;
631                    }
632                    transitionTo(mPending);
633                }
634                    break;
635                case STACK_EVENT:
636                    StackEvent event = (StackEvent) message.obj;
637                    switch (event.type) {
638                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
639                            processConnectionEvent(event.valueInt, event.device);
640                            break;
641                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
642                            processAudioStateEvent(event.valueInt, event.device);
643                            break;
644                        default:
645                            loge("Unexpected stack event: " + event.type);
646                            break;
647                    }
648                    break;
649                default:
650                    return NOT_HANDLED;
651            }
652            return retValue;
653        }
654
655        // in Connected state
656        private void processConnectionEvent(int state, BluetoothDevice device) {
657            switch (state) {
658                case CONNECTION_STATE_DISCONNECTED:
659                    if (mCurrentDevice.equals(device)) {
660                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
661                                                 BluetoothProfile.STATE_CONNECTED);
662                        synchronized (A2dpStateMachine.this) {
663                            mCurrentDevice = null;
664                            transitionTo(mDisconnected);
665                        }
666                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
667                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
668                                                 BluetoothProfile.STATE_CONNECTING);
669                        synchronized (A2dpStateMachine.this) {
670                            mTargetDevice = null;
671                        }
672                        logi("Disconnected from mTargetDevice in connected state device: " + device);
673                    } else {
674                        loge("Disconnected from unknown device: " + device);
675                    }
676                    break;
677              default:
678                  loge("Connection State Device: " + device + " bad state: " + state);
679                  break;
680            }
681        }
682        private void processAudioStateEvent(int state, BluetoothDevice device) {
683            if (!mCurrentDevice.equals(device)) {
684                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
685                                                           mCurrentDevice);
686                return;
687            }
688            switch (state) {
689                case AUDIO_STATE_STARTED:
690                    if (mPlayingA2dpDevice == null) {
691                        mPlayingA2dpDevice = device;
692                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
693                        broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
694                                            BluetoothA2dp.STATE_NOT_PLAYING);
695                    }
696                    break;
697                case AUDIO_STATE_REMOTE_SUSPEND:
698                case AUDIO_STATE_STOPPED:
699                    if (mPlayingA2dpDevice != null) {
700                        mPlayingA2dpDevice = null;
701                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
702                        broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
703                                            BluetoothA2dp.STATE_PLAYING);
704                    }
705                    break;
706                default:
707                  loge("Audio State Device: " + device + " bad state: " + state);
708                  break;
709            }
710        }
711    }
712
713    int getConnectionState(BluetoothDevice device) {
714        if (getCurrentState() == mDisconnected) {
715            return BluetoothProfile.STATE_DISCONNECTED;
716        }
717
718        synchronized (this) {
719            IState currentState = getCurrentState();
720            if (currentState == mPending) {
721                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
722                    return BluetoothProfile.STATE_CONNECTING;
723                }
724                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
725                    return BluetoothProfile.STATE_DISCONNECTING;
726                }
727                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
728                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
729                }
730                return BluetoothProfile.STATE_DISCONNECTED;
731            }
732
733            if (currentState == mConnected) {
734                if (mCurrentDevice.equals(device)) {
735                    return BluetoothProfile.STATE_CONNECTED;
736                }
737                return BluetoothProfile.STATE_DISCONNECTED;
738            } else {
739                loge("Bad currentState: " + currentState);
740                return BluetoothProfile.STATE_DISCONNECTED;
741            }
742        }
743    }
744
745    List<BluetoothDevice> getConnectedDevices() {
746        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
747        synchronized (this) {
748            if (getCurrentState() == mConnected) {
749                devices.add(mCurrentDevice);
750            }
751        }
752        return devices;
753    }
754
755    boolean isPlaying(BluetoothDevice device) {
756        synchronized (this) {
757            if (device.equals(mPlayingA2dpDevice)) {
758                return true;
759            }
760        }
761        return false;
762    }
763
764    BluetoothCodecStatus getCodecStatus() {
765        synchronized (this) {
766            return mCodecStatus;
767        }
768    }
769
770    private void onCodecConfigChanged(BluetoothCodecConfig newCodecConfig,
771            BluetoothCodecConfig[] codecsLocalCapabilities,
772            BluetoothCodecConfig[] codecsSelectableCapabilities) {
773        BluetoothCodecConfig prevCodecConfig = null;
774        synchronized (this) {
775            if (mCodecStatus != null) {
776                prevCodecConfig = mCodecStatus.getCodecConfig();
777            }
778            mCodecStatus = new BluetoothCodecStatus(
779                    newCodecConfig, codecsLocalCapabilities, codecsSelectableCapabilities);
780        }
781
782        Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
783        intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus);
784        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
785
786        log("A2DP Codec Config: " + prevCodecConfig + "->" + newCodecConfig);
787        for (BluetoothCodecConfig codecConfig : codecsLocalCapabilities) {
788            log("A2DP Codec Local Capability: " + codecConfig);
789        }
790        for (BluetoothCodecConfig codecConfig : codecsSelectableCapabilities) {
791            log("A2DP Codec Selectable Capability: " + codecConfig);
792        }
793
794        // Inform the Audio Service about the codec configuration change,
795        // so the Audio Service can reset accordingly the audio feeding
796        // parameters in the Audio HAL to the Bluetooth stack.
797        if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig) && (mCurrentDevice != null)
798                && (getCurrentState() == mConnected)) {
799            // Add the device only if it is currently connected
800            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mCurrentDevice);
801            mAudioManager.handleBluetoothA2dpDeviceConfigChange(mCurrentDevice);
802        }
803        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
804    }
805
806    void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
807        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
808        codecConfigArray[0] = codecConfig;
809        setCodecConfigPreferenceNative(codecConfigArray);
810    }
811
812    void enableOptionalCodecs() {
813        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
814        if (codecConfigArray == null) {
815            return;
816        }
817
818        // Set the mandatory codec's priority to default, and remove the rest
819        for (int i = 0; i < codecConfigArray.length; i++) {
820            BluetoothCodecConfig codecConfig = codecConfigArray[i];
821            if (!codecConfig.isMandatoryCodec()) {
822                codecConfigArray[i] = null;
823            }
824        }
825
826        setCodecConfigPreferenceNative(codecConfigArray);
827    }
828
829    void disableOptionalCodecs() {
830        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
831        if (codecConfigArray == null) {
832            return;
833        }
834        // Set the mandatory codec's priority to highest, and ignore the rest
835        for (int i = 0; i < codecConfigArray.length; i++) {
836            BluetoothCodecConfig codecConfig = codecConfigArray[i];
837            if (codecConfig.isMandatoryCodec()) {
838                codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
839            } else {
840                codecConfigArray[i] = null;
841            }
842        }
843        setCodecConfigPreferenceNative(codecConfigArray);
844    }
845
846    boolean okToConnect(BluetoothDevice device) {
847        AdapterService adapterService = AdapterService.getAdapterService();
848        int priority = mService.getPriority(device);
849        boolean ret = false;
850        //check if this is an incoming connection in Quiet mode.
851        if((adapterService == null) ||
852           ((adapterService.isQuietModeEnabled() == true) &&
853           (mTargetDevice == null))){
854            ret = false;
855        }
856        // check priority and accept or reject the connection. if priority is undefined
857        // it is likely that our SDP has not completed and peer is initiating the
858        // connection. Allow this connection, provided the device is bonded
859        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
860                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
861                (device.getBondState() != BluetoothDevice.BOND_NONE))){
862            ret= true;
863        }
864        return ret;
865    }
866
867    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
868        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
869        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
870        int connectionState;
871
872        for (BluetoothDevice device : bondedDevices) {
873            ParcelUuid[] featureUuids = device.getUuids();
874            if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
875                continue;
876            }
877            connectionState = getConnectionState(device);
878            for(int i = 0; i < states.length; i++) {
879                if (connectionState == states[i]) {
880                    deviceList.add(device);
881                }
882            }
883        }
884        return deviceList;
885    }
886
887
888    // This method does not check for error conditon (newState == prevState)
889    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
890
891        int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
892                BluetoothProfile.A2DP);
893
894        mWakeLock.acquire();
895        mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
896                                                        MSG_CONNECTION_STATE_CHANGED,
897                                                        prevState,
898                                                        newState,
899                                                        device),
900                                                        delay);
901    }
902
903    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
904        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
905        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
906        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
907        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
908        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
909        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
910
911        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
912    }
913
914    private byte[] getByteAddress(BluetoothDevice device) {
915        return Utils.getBytesFromAddress(device.getAddress());
916    }
917
918    private void onConnectionStateChanged(int state, byte[] address) {
919        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
920        event.valueInt = state;
921        event.device = getDevice(address);
922        sendMessage(STACK_EVENT, event);
923    }
924
925    private void onAudioStateChanged(int state, byte[] address) {
926        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
927        event.valueInt = state;
928        event.device = getDevice(address);
929        sendMessage(STACK_EVENT, event);
930    }
931    private BluetoothDevice getDevice(byte[] address) {
932        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
933    }
934
935    private class StackEvent {
936        int type = EVENT_TYPE_NONE;
937        int valueInt = 0;
938        BluetoothDevice device = null;
939
940        private StackEvent(int type) {
941            this.type = type;
942        }
943    }
944    /** Handles A2DP connection state change intent broadcasts. */
945    private class IntentBroadcastHandler extends Handler {
946
947        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
948            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
949            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
950            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
951            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
952            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
953            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
954            log("Connection state " + device + ": " + prevState + "->" + state);
955        }
956
957        @Override
958        public void handleMessage(Message msg) {
959            switch (msg.what) {
960                case MSG_CONNECTION_STATE_CHANGED:
961                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
962                    mWakeLock.release();
963                    break;
964            }
965        }
966    }
967
968    public void dump(StringBuilder sb) {
969        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
970        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
971        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
972        ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
973        ProfileService.println(sb, "StateMachine: " + this.toString());
974    }
975
976    // Event types for STACK_EVENT message
977    final private static int EVENT_TYPE_NONE = 0;
978    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
979    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
980
981   // Do not modify without updating the HAL bt_av.h files.
982
983    // match up with btav_connection_state_t enum of bt_av.h
984    final static int CONNECTION_STATE_DISCONNECTED = 0;
985    final static int CONNECTION_STATE_CONNECTING = 1;
986    final static int CONNECTION_STATE_CONNECTED = 2;
987    final static int CONNECTION_STATE_DISCONNECTING = 3;
988
989    // match up with btav_audio_state_t enum of bt_av.h
990    final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
991    final static int AUDIO_STATE_STOPPED = 1;
992    final static int AUDIO_STATE_STARTED = 2;
993
994    private native static void classInitNative();
995    private native void initNative(BluetoothCodecConfig[] codecConfigPriorites);
996    private native void cleanupNative();
997    private native boolean connectA2dpNative(byte[] address);
998    private native boolean disconnectA2dpNative(byte[] address);
999    private native boolean setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray);
1000}
1001