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