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