| /* |
| * Copyright (C) 2012 Google Inc. |
| */ |
| |
| /** |
| * 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 String TAG = "A2dpStateMachine"; |
| 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; |
| |
| private static final ParcelUuid[] A2DP_UUIDS = { |
| BluetoothUuid.AudioSink |
| }; |
| |
| // 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(TAG); |
| 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(TAG, "make"); |
| A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context); |
| a2dpSm.start(); |
| return a2dpSm; |
| } |
| |
| public void doQuit() { |
| 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) { |
| Log.e(TAG, "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: |
| Log.e(TAG, "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: |
| Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device); |
| break; |
| case CONNECTION_STATE_CONNECTING: |
| if (okToConnect(device)){ |
| Log.i(TAG,"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 |
| Log.i(TAG,"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: |
| Log.w(TAG, "A2DP Connected from Disconnected state"); |
| if (okToConnect(device)){ |
| Log.i(TAG,"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 |
| Log.i(TAG,"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: |
| Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device); |
| break; |
| default: |
| Log.e(TAG, "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: |
| Log.e(TAG, "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); |
| 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 { |
| Log.e(TAG, "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); |
| synchronized (A2dpStateMachine.this) { |
| mCurrentDevice = mIncomingDevice; |
| mIncomingDevice = null; |
| transitionTo(mConnected); |
| } |
| } else { |
| Log.e(TAG, "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)) { |
| Log.e(TAG, "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, ignore"); |
| } |
| 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)) { |
| Log.e(TAG, "TargetDevice is getting disconnected"); |
| } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { |
| Log.e(TAG, "IncomingDevice is getting disconnected"); |
| } else { |
| Log.e(TAG, "Disconnecting unknow device: " + device); |
| } |
| break; |
| default: |
| Log.e(TAG, "Incorrect state: " + state); |
| break; |
| } |
| } |
| |
| } |
| |
| private class Connected extends State { |
| @Override |
| public void enter() { |
| 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) { |
| Log.e(TAG, "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: |
| Log.e(TAG, "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 { |
| Log.e(TAG, "Disconnected from unknown device: " + device); |
| } |
| break; |
| default: |
| Log.e(TAG, "Connection State Device: " + device + " bad state: " + state); |
| break; |
| } |
| } |
| private void processAudioStateEvent(int state, BluetoothDevice device) { |
| if (!mCurrentDevice.equals(device)) { |
| Log.e(TAG, "Audio State Device:" + device + "is different from ConnectedDevice:" + |
| mCurrentDevice); |
| return; |
| } |
| switch (state) { |
| case AUDIO_STATE_STARTED: |
| if (mPlayingA2dpDevice == null) { |
| mPlayingA2dpDevice = device; |
| broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING, |
| BluetoothA2dp.STATE_NOT_PLAYING); |
| } |
| break; |
| case AUDIO_STATE_STOPPED: |
| if(mPlayingA2dpDevice != null) { |
| mPlayingA2dpDevice = null; |
| broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING, |
| BluetoothA2dp.STATE_PLAYING); |
| } |
| break; |
| default: |
| Log.e(TAG, "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 { |
| Log.e(TAG, "Bad currentState: " + currentState); |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| } |
| } |
| |
| List<BluetoothDevice> getConnectedDevices() { |
| List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); |
| 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<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); |
| Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); |
| int connectionState; |
| |
| for (BluetoothDevice device : bondedDevices) { |
| ParcelUuid[] featureUuids = device.getUuids(); |
| if (!BluetoothUuid.containsAnyUuid(featureUuids, A2DP_UUIDS)) { |
| 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); |
| |
| 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 void log(String msg) { |
| Log.d(TAG, msg); |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| |
| // 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); |
| } |