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