A2dpStateMachine.java revision 25ec7de5ff422f50f3dbfd0822423c85b47f4794
1/*
2 * Copyright (C) 2012 Google Inc.
3 */
4
5/**
6 * Bluetooth A2dp StateMachine
7 *                      (Disconnected)
8 *                           |    ^
9 *                   CONNECT |    | DISCONNECTED
10 *                           V    |
11 *                         (Pending)
12 *                           |    ^
13 *                 CONNECTED |    | CONNECT
14 *                           V    |
15 *                        (Connected)
16 */
17package com.android.bluetooth.a2dp;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothProfile;
23import android.bluetooth.BluetoothUuid;
24import android.bluetooth.IBluetooth;
25import android.content.Context;
26import android.content.Intent;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.ParcelUuid;
31import android.util.Log;
32import com.android.bluetooth.Utils;
33import com.android.internal.util.IState;
34import com.android.internal.util.State;
35import com.android.internal.util.StateMachine;
36import java.util.ArrayList;
37import java.util.List;
38import java.util.Set;
39
40final class A2dpStateMachine extends StateMachine {
41    private static final String TAG = "A2dpStateMachine";
42    private static final boolean DBG = true;
43
44    static final int CONNECT = 1;
45    static final int DISCONNECT = 2;
46    private static final int STACK_EVENT = 101;
47    private static final int CONNECT_TIMEOUT = 201;
48
49    private Disconnected mDisconnected;
50    private Pending mPending;
51    private Connected mConnected;
52
53    private Context mContext;
54    private BluetoothAdapter mAdapter;
55    private IBluetooth mAdapterService;
56    private static final ParcelUuid[] A2DP_UUIDS = {
57        BluetoothUuid.AudioSink
58    };
59
60    // mCurrentDevice is the device connected before the state changes
61    // mTargetDevice is the device to be connected
62    // mIncomingDevice is the device connecting to us, valid only in Pending state
63    //                when mIncomingDevice is not null, both mCurrentDevice
64    //                  and mTargetDevice are null
65    //                when either mCurrentDevice or mTargetDevice is not null,
66    //                  mIncomingDevice is null
67    // Stable states
68    //   No connection, Disconnected state
69    //                  both mCurrentDevice and mTargetDevice are null
70    //   Connected, Connected state
71    //              mCurrentDevice is not null, mTargetDevice is null
72    // Interim states
73    //   Connecting to a device, Pending
74    //                           mCurrentDevice is null, mTargetDevice is not null
75    //   Disconnecting device, Connecting to new device
76    //     Pending
77    //     Both mCurrentDevice and mTargetDevice are not null
78    //   Disconnecting device Pending
79    //                        mCurrentDevice is not null, mTargetDevice is null
80    //   Incoming connections Pending
81    //                        Both mCurrentDevice and mTargetDevice are null
82    private BluetoothDevice mCurrentDevice = null;
83    private BluetoothDevice mTargetDevice = null;
84    private BluetoothDevice mIncomingDevice = null;
85
86    static {
87        classInitNative();
88    }
89
90    A2dpStateMachine(Context context) {
91        super(TAG);
92        mContext = context;
93        mAdapter = BluetoothAdapter.getDefaultAdapter();
94        mAdapterService = IBluetooth.Stub.asInterface(ServiceManager.getService("bluetooth"));
95
96        initializeNativeDataNative();
97
98        mDisconnected = new Disconnected();
99        mPending = new Pending();
100        mConnected = new Connected();
101
102        addState(mDisconnected);
103        addState(mPending);
104        addState(mConnected);
105
106        setInitialState(mDisconnected);
107    }
108
109        private class Disconnected extends State {
110        @Override
111        public void enter() {
112            log("Enter Disconnected: " + getCurrentMessage().what);
113        }
114
115        @Override
116        public boolean processMessage(Message message) {
117            log("Disconnected process message: " + message.what);
118            if (DBG) {
119                if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
120                    log("ERROR: current, target, or mIncomingDevice not null in Disconnected");
121                    return NOT_HANDLED;
122                }
123            }
124
125            boolean retValue = HANDLED;
126            switch(message.what) {
127                case CONNECT:
128                    BluetoothDevice device = (BluetoothDevice) message.obj;
129                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
130                                   BluetoothProfile.STATE_DISCONNECTED);
131
132                    if (!connectA2dpNative(getByteAddress(device)) ) {
133                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
134                                       BluetoothProfile.STATE_CONNECTING);
135                        break;
136                    }
137
138                    synchronized (A2dpStateMachine.this) {
139                        mTargetDevice = device;
140                        transitionTo(mPending);
141                    }
142                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
143                    //          sends back events consistently
144                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
145                    break;
146                case DISCONNECT:
147                    // ignore
148                    break;
149                case STACK_EVENT:
150                    StackEvent event = (StackEvent) message.obj;
151                    switch (event.type) {
152                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
153                            processConnectionEvent(event.valueInt, event.device);
154                            break;
155                        default:
156                            Log.e(TAG, "Unexpected stack event: " + event.type);
157                            break;
158                    }
159                    break;
160                default:
161                    return NOT_HANDLED;
162            }
163            return retValue;
164        }
165
166        @Override
167        public void exit() {
168            log("Exit Disconnected: " + getCurrentMessage().what);
169        }
170
171        // in Disconnected state
172        private void processConnectionEvent(int state, BluetoothDevice device) {
173            switch (state) {
174            case CONNECTION_STATE_DISCONNECTED:
175                Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
176                break;
177            case CONNECTION_STATE_CONNECTING:
178                // TODO(BT) Assume it's incoming connection
179                //     Do we need to check priority and accept/reject accordingly?
180                broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
181                                         BluetoothProfile.STATE_DISCONNECTED);
182                synchronized (A2dpStateMachine.this) {
183                    mIncomingDevice = device;
184                    transitionTo(mPending);
185                }
186                break;
187            case CONNECTION_STATE_CONNECTED:
188                Log.w(TAG, "A2DP Connected from Disconnected state");
189                broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
190                                         BluetoothProfile.STATE_DISCONNECTED);
191                synchronized (A2dpStateMachine.this) {
192                    mCurrentDevice = device;
193                    transitionTo(mConnected);
194                }
195                break;
196            case CONNECTION_STATE_DISCONNECTING:
197                Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device);
198                break;
199            default:
200                Log.e(TAG, "Incorrect state: " + state);
201                break;
202            }
203        }
204    }
205
206    private class Pending extends State {
207        @Override
208        public void enter() {
209            log("Enter Pending: " + getCurrentMessage().what);
210        }
211
212        @Override
213        public boolean processMessage(Message message) {
214            log("Pending process message: " + message.what);
215
216            boolean retValue = HANDLED;
217            switch(message.what) {
218                case CONNECT:
219                    deferMessage(message);
220                    break;
221                case CONNECT_TIMEOUT:
222                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
223                                             getByteAddress(mTargetDevice));
224                    break;
225                case DISCONNECT:
226                    BluetoothDevice device = (BluetoothDevice) message.obj;
227                    if (mCurrentDevice != null && mTargetDevice != null &&
228                        mTargetDevice.equals(device) ) {
229                        // cancel connection to the mTargetDevice
230                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
231                                       BluetoothProfile.STATE_CONNECTING);
232                        synchronized (A2dpStateMachine.this) {
233                            mTargetDevice = null;
234                        }
235                    } else {
236                        deferMessage(message);
237                    }
238                    break;
239                case STACK_EVENT:
240                    StackEvent event = (StackEvent) message.obj;
241                    switch (event.type) {
242                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
243                            removeMessages(CONNECT_TIMEOUT);
244                            processConnectionEvent(event.valueInt, event.device);
245                            break;
246                        default:
247                            Log.e(TAG, "Unexpected stack event: " + event.type);
248                            break;
249                    }
250                    break;
251                default:
252                    return NOT_HANDLED;
253            }
254            return retValue;
255        }
256
257        // in Pending state
258        private void processConnectionEvent(int state, BluetoothDevice device) {
259            switch (state) {
260                case CONNECTION_STATE_DISCONNECTED:
261                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
262                        broadcastConnectionState(mCurrentDevice,
263                                                 BluetoothProfile.STATE_DISCONNECTED,
264                                                 BluetoothProfile.STATE_DISCONNECTING);
265                        synchronized (A2dpStateMachine.this) {
266                            mCurrentDevice = null;
267                        }
268
269                        if (mTargetDevice != null) {
270                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
271                                broadcastConnectionState(mTargetDevice,
272                                                         BluetoothProfile.STATE_DISCONNECTED,
273                                                         BluetoothProfile.STATE_CONNECTING);
274                                synchronized (A2dpStateMachine.this) {
275                                    mTargetDevice = null;
276                                    transitionTo(mDisconnected);
277                                }
278                            }
279                        } else {
280                            synchronized (A2dpStateMachine.this) {
281                                mIncomingDevice = null;
282                                transitionTo(mDisconnected);
283                            }
284                        }
285                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
286                        // outgoing connection failed
287                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
288                                                 BluetoothProfile.STATE_CONNECTING);
289                        synchronized (A2dpStateMachine.this) {
290                            mTargetDevice = null;
291                            transitionTo(mDisconnected);
292                        }
293                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
294                        broadcastConnectionState(mIncomingDevice,
295                                                 BluetoothProfile.STATE_DISCONNECTED,
296                                                 BluetoothProfile.STATE_CONNECTING);
297                        synchronized (A2dpStateMachine.this) {
298                            mIncomingDevice = null;
299                            transitionTo(mDisconnected);
300                        }
301                    } else {
302                        Log.e(TAG, "Unknown device Disconnected: " + device);
303                    }
304                    break;
305            case CONNECTION_STATE_CONNECTED:
306                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
307                    // disconnection failed
308                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
309                                             BluetoothProfile.STATE_DISCONNECTING);
310                    if (mTargetDevice != null) {
311                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
312                                                 BluetoothProfile.STATE_CONNECTING);
313                    }
314                    synchronized (A2dpStateMachine.this) {
315                        mTargetDevice = null;
316                        transitionTo(mConnected);
317                    }
318                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
319                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
320                                             BluetoothProfile.STATE_CONNECTING);
321                    synchronized (A2dpStateMachine.this) {
322                        mCurrentDevice = mTargetDevice;
323                        mTargetDevice = null;
324                        transitionTo(mConnected);
325                    }
326                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
327                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
328                                             BluetoothProfile.STATE_CONNECTING);
329                    synchronized (A2dpStateMachine.this) {
330                        mCurrentDevice = mIncomingDevice;
331                        mIncomingDevice = null;
332                        transitionTo(mConnected);
333                    }
334                } else {
335                    Log.e(TAG, "Unknown device Connected: " + device);
336                    // something is wrong here, but sync our state with stack
337                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
338                                             BluetoothProfile.STATE_DISCONNECTED);
339                    synchronized (A2dpStateMachine.this) {
340                        mCurrentDevice = device;
341                        mTargetDevice = null;
342                        mIncomingDevice = null;
343                        transitionTo(mConnected);
344                    }
345                }
346                break;
347            case CONNECTION_STATE_CONNECTING:
348                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
349                    log("current device tries to connect back");
350                    // TODO(BT) ignore or reject
351                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
352                    // The stack is connecting to target device or
353                    // there is an incoming connection from the target device at the same time
354                    // we already broadcasted the intent, doing nothing here
355                    if (DBG) {
356                        log("Stack and target device are connecting");
357                    }
358                }
359                else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
360                    Log.e(TAG, "Another connecting event on the incoming device");
361                } else {
362                    // We get an incoming connecting request while Pending
363                    // TODO(BT) is stack handing this case? let's ignore it for now
364                    log("Incoming connection while pending, ignore");
365                }
366                break;
367            case CONNECTION_STATE_DISCONNECTING:
368                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
369                    // we already broadcasted the intent, doing nothing here
370                    if (DBG) {
371                        log("stack is disconnecting mCurrentDevice");
372                    }
373                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
374                    Log.e(TAG, "TargetDevice is getting disconnected");
375                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
376                    Log.e(TAG, "IncomingDevice is getting disconnected");
377                } else {
378                    Log.e(TAG, "Disconnecting unknow device: " + device);
379                }
380                break;
381            default:
382                Log.e(TAG, "Incorrect state: " + state);
383                break;
384            }
385        }
386
387    }
388
389    private class Connected extends State {
390        @Override
391        public void enter() {
392            log("Enter Connected: " + getCurrentMessage().what);
393        }
394
395        @Override
396        public boolean processMessage(Message message) {
397            log("Connected process message: " + message.what);
398            if (DBG) {
399                if (mCurrentDevice == null) {
400                    log("ERROR: mCurrentDevice is null in Connected");
401                    return NOT_HANDLED;
402                }
403            }
404
405            boolean retValue = HANDLED;
406            switch(message.what) {
407                case CONNECT:
408                {
409                    BluetoothDevice device = (BluetoothDevice) message.obj;
410                    if (mCurrentDevice.equals(device)) {
411                        break;
412                    }
413
414                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
415                                   BluetoothProfile.STATE_DISCONNECTED);
416                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
417                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
418                                       BluetoothProfile.STATE_CONNECTING);
419                        break;
420                    }
421
422                    synchronized (A2dpStateMachine.this) {
423                        mTargetDevice = device;
424                        transitionTo(mPending);
425                    }
426                }
427                    break;
428                case DISCONNECT:
429                {
430                    BluetoothDevice device = (BluetoothDevice) message.obj;
431                    if (!mCurrentDevice.equals(device)) {
432                        break;
433                    }
434                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
435                                   BluetoothProfile.STATE_CONNECTED);
436                    if (!disconnectA2dpNative(getByteAddress(device))) {
437                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
438                                       BluetoothProfile.STATE_DISCONNECTED);
439                        break;
440                    }
441                    transitionTo(mPending);
442                }
443                    break;
444                case STACK_EVENT:
445                    StackEvent event = (StackEvent) message.obj;
446                    switch (event.type) {
447                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
448                            processConnectionEvent(event.valueInt, event.device);
449                            break;
450                        default:
451                            Log.e(TAG, "Unexpected stack event: " + event.type);
452                            break;
453                    }
454                    break;
455                default:
456                    return NOT_HANDLED;
457            }
458            return retValue;
459        }
460
461        // in Connected state
462        private void processConnectionEvent(int state, BluetoothDevice device) {
463            switch (state) {
464                case CONNECTION_STATE_DISCONNECTED:
465                    if (mCurrentDevice.equals(device)) {
466                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
467                                                 BluetoothProfile.STATE_CONNECTED);
468                        synchronized (A2dpStateMachine.this) {
469                            mCurrentDevice = null;
470                            transitionTo(mDisconnected);
471                        }
472                    } else {
473                        Log.e(TAG, "Disconnected from unknown device: " + device);
474                    }
475                    break;
476              default:
477                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
478                  break;
479            }
480        }
481    }
482
483    int getConnectionState(BluetoothDevice device) {
484        if (getCurrentState() == mDisconnected) {
485            return BluetoothProfile.STATE_DISCONNECTED;
486        }
487
488        synchronized (this) {
489            IState currentState = getCurrentState();
490            if (currentState == mPending) {
491                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
492                    return BluetoothProfile.STATE_CONNECTING;
493                }
494                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
495                    return BluetoothProfile.STATE_DISCONNECTING;
496                }
497                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
498                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
499                }
500                return BluetoothProfile.STATE_DISCONNECTED;
501            }
502
503            if (currentState == mConnected) {
504                if (mCurrentDevice.equals(device)) {
505                    return BluetoothProfile.STATE_CONNECTED;
506                }
507                return BluetoothProfile.STATE_DISCONNECTED;
508            } else {
509                Log.e(TAG, "Bad currentState: " + currentState);
510                return BluetoothProfile.STATE_DISCONNECTED;
511            }
512        }
513    }
514
515    List<BluetoothDevice> getConnectedDevices() {
516        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
517        synchronized(this) {
518            if (getCurrentState() == mConnected) {
519                devices.add(mCurrentDevice);
520            }
521        }
522        return devices;
523    }
524
525    boolean isPlaying(BluetoothDevice device) {
526        synchronized(this) {
527            if (getCurrentState() == mConnected && mCurrentDevice.equals(device)) {
528                return true;
529            }
530        }
531        return false;
532    }
533
534    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
535        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
536        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
537        int connectionState;
538
539        for (BluetoothDevice device : bondedDevices) {
540            ParcelUuid[] featureUuids = device.getUuids();
541            if (!BluetoothUuid.containsAnyUuid(featureUuids, A2DP_UUIDS)) {
542                continue;
543            }
544            connectionState = getConnectionState(device);
545            for(int i = 0; i < states.length; i++) {
546                if (connectionState == states[i]) {
547                    deviceList.add(device);
548                }
549            }
550        }
551        return deviceList;
552    }
553
554    // This method does not check for error conditon (newState == prevState)
555    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
556        Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
557        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
558        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
559        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
560        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
561        if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState);
562        try {
563            mAdapterService.sendConnectionStateChange(device, BluetoothProfile.A2DP, newState,
564                                                      prevState);
565        } catch (RemoteException e) {
566            Log.e(TAG, Log.getStackTraceString(new Throwable()));
567        }
568    }
569
570    private byte[] getByteAddress(BluetoothDevice device) {
571        return Utils.getBytesFromAddress(device.getAddress());
572    }
573
574    private void onConnectionStateChanged(int state, byte[] address) {
575        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
576        event.valueInt = state;
577        event.device = getDevice(address);
578        sendMessage(STACK_EVENT, event);
579    }
580
581    private BluetoothDevice getDevice(byte[] address) {
582        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
583    }
584
585    private void log(String msg) {
586        if (DBG) {
587            Log.d(TAG, msg);
588        }
589    }
590
591    private class StackEvent {
592        int type = EVENT_TYPE_NONE;
593        int valueInt = 0;
594        BluetoothDevice device = null;
595
596        private StackEvent(int type) {
597            this.type = type;
598        }
599    }
600
601    // Event types for STACK_EVENT message
602    final private static int EVENT_TYPE_NONE = 0;
603    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
604
605   // Do not modify without upating the HAL bt_av.h files.
606
607    // match up with btav_connection_state_t enum of bt_av.h
608    final static int CONNECTION_STATE_DISCONNECTED = 0;
609    final static int CONNECTION_STATE_CONNECTING = 1;
610    final static int CONNECTION_STATE_CONNECTED = 2;
611    final static int CONNECTION_STATE_DISCONNECTING = 3;
612
613    private native static void classInitNative();
614    private native void initializeNativeDataNative();
615    private native boolean connectA2dpNative(byte[] address);
616    private native boolean disconnectA2dpNative(byte[] address);
617}
618