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