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