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