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