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