/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Bluetooth A2dp StateMachine * (Disconnected) * | ^ * CONNECT | | DISCONNECTED * V | * (Pending) * | ^ * CONNECTED | | CONNECT * V | * (Connected) */ package com.android.bluetooth.a2dp; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetooth; import android.content.Context; import android.media.AudioManager; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.content.Intent; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ParcelUuid; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import java.util.ArrayList; import java.util.List; import java.util.Set; final class A2dpStateMachine extends StateMachine { private static final boolean DBG = false; static final int CONNECT = 1; static final int DISCONNECT = 2; private static final int STACK_EVENT = 101; private static final int CONNECT_TIMEOUT = 201; private Disconnected mDisconnected; private Pending mPending; private Connected mConnected; private A2dpService mService; private Context mContext; private BluetoothAdapter mAdapter; private final AudioManager mAudioManager; private IntentBroadcastHandler mIntentBroadcastHandler; private final WakeLock mWakeLock; private static final int MSG_CONNECTION_STATE_CHANGED = 0; // mCurrentDevice is the device connected before the state changes // mTargetDevice is the device to be connected // mIncomingDevice is the device connecting to us, valid only in Pending state // when mIncomingDevice is not null, both mCurrentDevice // and mTargetDevice are null // when either mCurrentDevice or mTargetDevice is not null, // mIncomingDevice is null // Stable states // No connection, Disconnected state // both mCurrentDevice and mTargetDevice are null // Connected, Connected state // mCurrentDevice is not null, mTargetDevice is null // Interim states // Connecting to a device, Pending // mCurrentDevice is null, mTargetDevice is not null // Disconnecting device, Connecting to new device // Pending // Both mCurrentDevice and mTargetDevice are not null // Disconnecting device Pending // mCurrentDevice is not null, mTargetDevice is null // Incoming connections Pending // Both mCurrentDevice and mTargetDevice are null private BluetoothDevice mCurrentDevice = null; private BluetoothDevice mTargetDevice = null; private BluetoothDevice mIncomingDevice = null; private BluetoothDevice mPlayingA2dpDevice = null; static { classInitNative(); } private A2dpStateMachine(A2dpService svc, Context context) { super("A2dpStateMachine"); mService = svc; mContext = context; mAdapter = BluetoothAdapter.getDefaultAdapter(); initNative(); mDisconnected = new Disconnected(); mPending = new Pending(); mConnected = new Connected(); addState(mDisconnected); addState(mPending); addState(mConnected); setInitialState(mDisconnected); PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService"); mIntentBroadcastHandler = new IntentBroadcastHandler(); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } static A2dpStateMachine make(A2dpService svc, Context context) { Log.d("A2dpStateMachine", "make"); A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context); a2dpSm.start(); return a2dpSm; } public void doQuit() { if ((mTargetDevice != null) && (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) { log("doQuit()- Move A2DP State to DISCONNECTED"); broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); } quitNow(); } public void cleanup() { cleanupNative(); } private class Disconnected extends State { @Override public void enter() { log("Enter Disconnected: " + getCurrentMessage().what); } @Override public boolean processMessage(Message message) { log("Disconnected process message: " + message.what); if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) { loge("ERROR: current, target, or mIncomingDevice not null in Disconnected"); return NOT_HANDLED; } boolean retValue = HANDLED; switch(message.what) { case CONNECT: BluetoothDevice device = (BluetoothDevice) message.obj; broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); if (!connectA2dpNative(getByteAddress(device)) ) { broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); break; } synchronized (A2dpStateMachine.this) { mTargetDevice = device; transitionTo(mPending); } // TODO(BT) remove CONNECT_TIMEOUT when the stack // sends back events consistently sendMessageDelayed(CONNECT_TIMEOUT, 30000); break; case DISCONNECT: // ignore break; case STACK_EVENT: StackEvent event = (StackEvent) message.obj; switch (event.type) { case EVENT_TYPE_CONNECTION_STATE_CHANGED: processConnectionEvent(event.valueInt, event.device); break; default: loge("Unexpected stack event: " + event.type); break; } break; default: return NOT_HANDLED; } return retValue; } @Override public void exit() { log("Exit Disconnected: " + getCurrentMessage().what); } // in Disconnected state private void processConnectionEvent(int state, BluetoothDevice device) { switch (state) { case CONNECTION_STATE_DISCONNECTED: logw("Ignore HF DISCONNECTED event, device: " + device); break; case CONNECTION_STATE_CONNECTING: if (okToConnect(device)){ logi("Incoming A2DP accepted"); broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); synchronized (A2dpStateMachine.this) { mIncomingDevice = device; transitionTo(mPending); } } else { //reject the connection and stay in Disconnected state itself logi("Incoming A2DP rejected"); disconnectA2dpNative(getByteAddress(device)); // the other profile connection should be initiated AdapterService adapterService = AdapterService.getAdapterService(); if (adapterService != null) { adapterService.connectOtherProfile(device, AdapterService.PROFILE_CONN_REJECTED); } } break; case CONNECTION_STATE_CONNECTED: logw("A2DP Connected from Disconnected state"); if (okToConnect(device)){ logi("Incoming A2DP accepted"); broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); synchronized (A2dpStateMachine.this) { mCurrentDevice = device; transitionTo(mConnected); } } else { //reject the connection and stay in Disconnected state itself logi("Incoming A2DP rejected"); disconnectA2dpNative(getByteAddress(device)); // the other profile connection should be initiated AdapterService adapterService = AdapterService.getAdapterService(); if (adapterService != null) { adapterService.connectOtherProfile(device, AdapterService.PROFILE_CONN_REJECTED); } } break; case CONNECTION_STATE_DISCONNECTING: logw("Ignore A2dp DISCONNECTING event, device: " + device); break; default: loge("Incorrect state: " + state); break; } } } private class Pending extends State { @Override public void enter() { log("Enter Pending: " + getCurrentMessage().what); } @Override public boolean processMessage(Message message) { log("Pending process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { case CONNECT: deferMessage(message); break; case CONNECT_TIMEOUT: onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED, getByteAddress(mTargetDevice)); break; case DISCONNECT: BluetoothDevice device = (BluetoothDevice) message.obj; if (mCurrentDevice != null && mTargetDevice != null && mTargetDevice.equals(device) ) { // cancel connection to the mTargetDevice broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); synchronized (A2dpStateMachine.this) { mTargetDevice = null; } } else { deferMessage(message); } break; case STACK_EVENT: StackEvent event = (StackEvent) message.obj; switch (event.type) { case EVENT_TYPE_CONNECTION_STATE_CHANGED: removeMessages(CONNECT_TIMEOUT); processConnectionEvent(event.valueInt, event.device); break; default: loge("Unexpected stack event: " + event.type); break; } break; default: return NOT_HANDLED; } return retValue; } // in Pending state private void processConnectionEvent(int state, BluetoothDevice device) { switch (state) { case CONNECTION_STATE_DISCONNECTED: if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING); synchronized (A2dpStateMachine.this) { mCurrentDevice = null; } if (mTargetDevice != null) { if (!connectA2dpNative(getByteAddress(mTargetDevice))) { broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); synchronized (A2dpStateMachine.this) { mTargetDevice = null; transitionTo(mDisconnected); } } } else { synchronized (A2dpStateMachine.this) { mIncomingDevice = null; transitionTo(mDisconnected); } } } else if (mTargetDevice != null && mTargetDevice.equals(device)) { // outgoing connection failed broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); // check if there is some incoming connection request if (mIncomingDevice != null) { logi("disconnect for outgoing in pending state"); synchronized (A2dpStateMachine.this) { mTargetDevice = null; } break; } synchronized (A2dpStateMachine.this) { mTargetDevice = null; transitionTo(mDisconnected); } } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); synchronized (A2dpStateMachine.this) { mIncomingDevice = null; transitionTo(mDisconnected); } } else { loge("Unknown device Disconnected: " + device); } break; case CONNECTION_STATE_CONNECTED: if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { // disconnection failed broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTING); if (mTargetDevice != null) { broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); } synchronized (A2dpStateMachine.this) { mTargetDevice = null; transitionTo(mConnected); } } else if (mTargetDevice != null && mTargetDevice.equals(device)) { broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); synchronized (A2dpStateMachine.this) { mCurrentDevice = mTargetDevice; mTargetDevice = null; transitionTo(mConnected); } } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); // check for a2dp connection allowed for this device in race condition if (okToConnect(mIncomingDevice)) { logi("Ready to connect incoming Connection from pending state"); synchronized (A2dpStateMachine.this) { mCurrentDevice = mIncomingDevice; mIncomingDevice = null; transitionTo(mConnected); } } else { // A2dp connection unchecked for this device loge("Incoming A2DP rejected from pending state"); disconnectA2dpNative(getByteAddress(device)); } } else { loge("Unknown device Connected: " + device); // something is wrong here, but sync our state with stack broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); synchronized (A2dpStateMachine.this) { mCurrentDevice = device; mTargetDevice = null; mIncomingDevice = null; transitionTo(mConnected); } } break; case CONNECTION_STATE_CONNECTING: if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { log("current device tries to connect back"); // TODO(BT) ignore or reject } else if (mTargetDevice != null && mTargetDevice.equals(device)) { // The stack is connecting to target device or // there is an incoming connection from the target device at the same time // we already broadcasted the intent, doing nothing here log("Stack and target device are connecting"); } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { loge("Another connecting event on the incoming device"); } else { // We get an incoming connecting request while Pending // TODO(BT) is stack handing this case? let's ignore it for now log("Incoming connection while pending, accept it"); broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); mIncomingDevice = device; } break; case CONNECTION_STATE_DISCONNECTING: if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { // we already broadcasted the intent, doing nothing here if (DBG) { log("stack is disconnecting mCurrentDevice"); } } else if (mTargetDevice != null && mTargetDevice.equals(device)) { loge("TargetDevice is getting disconnected"); } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { loge("IncomingDevice is getting disconnected"); } else { loge("Disconnecting unknow device: " + device); } break; default: loge("Incorrect state: " + state); break; } } } private class Connected extends State { @Override public void enter() { // Remove pending connection attempts that were deferred during the pending // state. This is to prevent auto connect attempts from disconnecting // devices that previously successfully connected. // TODO: This needs to check for multiple A2DP connections, once supported... removeDeferredMessages(CONNECT); log("Enter Connected: " + getCurrentMessage().what); // Upon connected, the audio starts out as stopped broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING, BluetoothA2dp.STATE_PLAYING); } @Override public boolean processMessage(Message message) { log("Connected process message: " + message.what); if (mCurrentDevice == null) { loge("ERROR: mCurrentDevice is null in Connected"); return NOT_HANDLED; } boolean retValue = HANDLED; switch(message.what) { case CONNECT: { BluetoothDevice device = (BluetoothDevice) message.obj; if (mCurrentDevice.equals(device)) { break; } broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) { broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); break; } synchronized (A2dpStateMachine.this) { mTargetDevice = device; transitionTo(mPending); } } break; case DISCONNECT: { BluetoothDevice device = (BluetoothDevice) message.obj; if (!mCurrentDevice.equals(device)) { break; } broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED); if (!disconnectA2dpNative(getByteAddress(device))) { broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); break; } transitionTo(mPending); } break; case STACK_EVENT: StackEvent event = (StackEvent) message.obj; switch (event.type) { case EVENT_TYPE_CONNECTION_STATE_CHANGED: processConnectionEvent(event.valueInt, event.device); break; case EVENT_TYPE_AUDIO_STATE_CHANGED: processAudioStateEvent(event.valueInt, event.device); break; default: loge("Unexpected stack event: " + event.type); break; } break; default: return NOT_HANDLED; } return retValue; } // in Connected state private void processConnectionEvent(int state, BluetoothDevice device) { switch (state) { case CONNECTION_STATE_DISCONNECTED: if (mCurrentDevice.equals(device)) { broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); synchronized (A2dpStateMachine.this) { mCurrentDevice = null; transitionTo(mDisconnected); } } else if (mTargetDevice != null && mTargetDevice.equals(device)) { broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); synchronized (A2dpStateMachine.this) { mTargetDevice = null; } logi("Disconnected from mTargetDevice in connected state device: " + device); } else { loge("Disconnected from unknown device: " + device); } break; default: loge("Connection State Device: " + device + " bad state: " + state); break; } } private void processAudioStateEvent(int state, BluetoothDevice device) { if (!mCurrentDevice.equals(device)) { loge("Audio State Device:" + device + "is different from ConnectedDevice:" + mCurrentDevice); return; } switch (state) { case AUDIO_STATE_STARTED: if (mPlayingA2dpDevice == null) { mPlayingA2dpDevice = device; mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING); broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING, BluetoothA2dp.STATE_NOT_PLAYING); } break; case AUDIO_STATE_REMOTE_SUSPEND: case AUDIO_STATE_STOPPED: if (mPlayingA2dpDevice != null) { mPlayingA2dpDevice = null; mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING); broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING, BluetoothA2dp.STATE_PLAYING); } break; default: loge("Audio State Device: " + device + " bad state: " + state); break; } } } int getConnectionState(BluetoothDevice device) { if (getCurrentState() == mDisconnected) { return BluetoothProfile.STATE_DISCONNECTED; } synchronized (this) { IState currentState = getCurrentState(); if (currentState == mPending) { if ((mTargetDevice != null) && mTargetDevice.equals(device)) { return BluetoothProfile.STATE_CONNECTING; } if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { return BluetoothProfile.STATE_DISCONNECTING; } if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) { return BluetoothProfile.STATE_CONNECTING; // incoming connection } return BluetoothProfile.STATE_DISCONNECTED; } if (currentState == mConnected) { if (mCurrentDevice.equals(device)) { return BluetoothProfile.STATE_CONNECTED; } return BluetoothProfile.STATE_DISCONNECTED; } else { loge("Bad currentState: " + currentState); return BluetoothProfile.STATE_DISCONNECTED; } } } List getConnectedDevices() { List devices = new ArrayList(); synchronized(this) { if (getCurrentState() == mConnected) { devices.add(mCurrentDevice); } } return devices; } boolean isPlaying(BluetoothDevice device) { synchronized(this) { if (device.equals(mPlayingA2dpDevice)) { return true; } } return false; } boolean okToConnect(BluetoothDevice device) { AdapterService adapterService = AdapterService.getAdapterService(); int priority = mService.getPriority(device); boolean ret = false; //check if this is an incoming connection in Quiet mode. if((adapterService == null) || ((adapterService.isQuietModeEnabled() == true) && (mTargetDevice == null))){ ret = false; } // check priority and accept or reject the connection. if priority is undefined // it is likely that our SDP has not completed and peer is initiating the // connection. Allow this connection, provided the device is bonded else if((BluetoothProfile.PRIORITY_OFF < priority) || ((BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState() != BluetoothDevice.BOND_NONE))){ ret= true; } return ret; } synchronized List getDevicesMatchingConnectionStates(int[] states) { List deviceList = new ArrayList(); Set bondedDevices = mAdapter.getBondedDevices(); int connectionState; for (BluetoothDevice device : bondedDevices) { ParcelUuid[] featureUuids = device.getUuids(); if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) { continue; } connectionState = getConnectionState(device); for(int i = 0; i < states.length; i++) { if (connectionState == states[i]) { deviceList.add(device); } } } return deviceList; } // This method does not check for error conditon (newState == prevState) private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) { int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState, BluetoothProfile.A2DP); mWakeLock.acquire(); mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage( MSG_CONNECTION_STATE_CHANGED, prevState, newState, device), delay); } private void broadcastAudioState(BluetoothDevice device, int state, int prevState) { Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); } private byte[] getByteAddress(BluetoothDevice device) { return Utils.getBytesFromAddress(device.getAddress()); } private void onConnectionStateChanged(int state, byte[] address) { StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED); event.valueInt = state; event.device = getDevice(address); sendMessage(STACK_EVENT, event); } private void onAudioStateChanged(int state, byte[] address) { StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED); event.valueInt = state; event.device = getDevice(address); sendMessage(STACK_EVENT, event); } private BluetoothDevice getDevice(byte[] address) { return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); } private class StackEvent { int type = EVENT_TYPE_NONE; int valueInt = 0; BluetoothDevice device = null; private StackEvent(int type) { this.type = type; } } /** Handles A2DP connection state change intent broadcasts. */ private class IntentBroadcastHandler extends Handler { private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, state); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); log("Connection state " + device + ": " + prevState + "->" + state); mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.A2DP, state, prevState); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_CONNECTION_STATE_CHANGED: onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2); mWakeLock.release(); break; } } } public void dump(StringBuilder sb) { ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice); ProfileService.println(sb, "mTargetDevice: " + mTargetDevice); ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice); ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice); ProfileService.println(sb, "StateMachine: " + this.toString()); } // Event types for STACK_EVENT message final private static int EVENT_TYPE_NONE = 0; final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2; // Do not modify without updating the HAL bt_av.h files. // match up with btav_connection_state_t enum of bt_av.h final static int CONNECTION_STATE_DISCONNECTED = 0; final static int CONNECTION_STATE_CONNECTING = 1; final static int CONNECTION_STATE_CONNECTED = 2; final static int CONNECTION_STATE_DISCONNECTING = 3; // match up with btav_audio_state_t enum of bt_av.h final static int AUDIO_STATE_REMOTE_SUSPEND = 0; final static int AUDIO_STATE_STOPPED = 1; final static int AUDIO_STATE_STARTED = 2; private native static void classInitNative(); private native void initNative(); private native void cleanupNative(); private native boolean connectA2dpNative(byte[] address); private native boolean disconnectA2dpNative(byte[] address); }