summaryrefslogtreecommitdiffstats
path: root/core/java/android/server/BluetoothA2dpService.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/server/BluetoothA2dpService.java')
-rw-r--r--core/java/android/server/BluetoothA2dpService.java597
1 files changed, 302 insertions, 295 deletions
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 5c4e56d..ec3b2ff 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -23,17 +23,16 @@
package android.server;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothError;
-import android.bluetooth.BluetoothIntent;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothA2dp;
+import android.os.ParcelUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.media.AudioManager;
-import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
@@ -41,9 +40,9 @@ import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String TAG = "BluetoothA2dpService";
@@ -54,386 +53,423 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
- private static final String A2DP_SINK_ADDRESS = "a2dp_sink_address";
private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
private static final int MESSAGE_CONNECT_TO = 1;
- private static final int MESSAGE_DISCONNECT = 2;
- private final Context mContext;
- private final IntentFilter mIntentFilter;
- private HashMap<String, SinkState> mAudioDevices;
- private final AudioManager mAudioManager;
- private final BluetoothDevice mBluetooth;
-
- // list of disconnected sinks to process after a delay
- private final ArrayList<String> mPendingDisconnects = new ArrayList<String>();
- // number of active sinks
- private int mSinkCount = 0;
-
- private class SinkState {
- public String address;
- public int state;
- public SinkState(String a, int s) {address = a; state = s;}
- }
-
- public BluetoothA2dpService(Context context) {
- mContext = context;
-
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
- mBluetooth = (BluetoothDevice) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
- if (mBluetooth == null) {
- throw new RuntimeException("Platform does not support Bluetooth");
- }
-
- if (!initNative()) {
- throw new RuntimeException("Could not init BluetoothA2dpService");
- }
+ private static final String PROPERTY_STATE = "State";
- mIntentFilter = new IntentFilter(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
- mContext.registerReceiver(mReceiver, mIntentFilter);
+ private static final String SINK_STATE_DISCONNECTED = "disconnected";
+ private static final String SINK_STATE_CONNECTING = "connecting";
+ private static final String SINK_STATE_CONNECTED = "connected";
+ private static final String SINK_STATE_PLAYING = "playing";
- if (mBluetooth.isEnabled()) {
- onBluetoothEnable();
- }
- }
+ private static int mSinkCount;
- @Override
- protected void finalize() throws Throwable {
- try {
- cleanupNative();
- } finally {
- super.finalize();
- }
- }
+ private final Context mContext;
+ private final IntentFilter mIntentFilter;
+ private HashMap<BluetoothDevice, Integer> mAudioDevices;
+ private final AudioManager mAudioManager;
+ private final BluetoothService mBluetoothService;
+ private final BluetoothAdapter mAdapter;
+ private int mTargetA2dpState;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
- if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
- int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE,
- BluetoothError.ERROR);
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
switch (state) {
- case BluetoothDevice.BLUETOOTH_STATE_ON:
+ case BluetoothAdapter.STATE_ON:
onBluetoothEnable();
break;
- case BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
onBluetoothDisable();
break;
}
- } else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) {
- int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE,
- BluetoothError.ERROR);
+ } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
switch(bondState) {
case BluetoothDevice.BOND_BONDED:
- setSinkPriority(address, BluetoothA2dp.PRIORITY_AUTO);
+ setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO);
break;
case BluetoothDevice.BOND_BONDING:
- case BluetoothDevice.BOND_NOT_BONDED:
- setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF);
+ case BluetoothDevice.BOND_NONE:
+ setSinkPriority(device, BluetoothA2dp.PRIORITY_OFF);
break;
}
- } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
- if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF) {
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+ if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
+ isSinkDevice(device)) {
// This device is a preferred sink. Make an A2DP connection
// after a delay. We delay to avoid connection collisions,
// and to give other profiles such as HFP a chance to
// connect first.
- Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, address);
+ Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, device);
mHandler.sendMessageDelayed(msg, 6000);
}
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+ synchronized (this) {
+ if (mAudioDevices.containsKey(device)) {
+ int state = mAudioDevices.get(device);
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+ }
}
}
};
+ public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
+ mContext = context;
+
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ mBluetoothService = bluetoothService;
+ if (mBluetoothService == null) {
+ throw new RuntimeException("Platform does not support Bluetooth");
+ }
+
+ if (!initNative()) {
+ throw new RuntimeException("Could not init BluetoothA2dpService");
+ }
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+ mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+
+ mAudioDevices = new HashMap<BluetoothDevice, Integer>();
+
+ if (mBluetoothService.isEnabled())
+ onBluetoothEnable();
+ mTargetA2dpState = -1;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNative();
+ } finally {
+ super.finalize();
+ }
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CONNECT_TO:
- String address = (String)msg.obj;
+ BluetoothDevice device = (BluetoothDevice) msg.obj;
// check bluetooth is still on, device is still preferred, and
// nothing is currently connected
- if (mBluetooth.isEnabled() &&
- getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF &&
+ if (mBluetoothService.isEnabled() &&
+ getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
lookupSinksMatchingStates(new int[] {
BluetoothA2dp.STATE_CONNECTING,
BluetoothA2dp.STATE_CONNECTED,
BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
- log("Auto-connecting A2DP to sink " + address);
- connectSink(address);
+ log("Auto-connecting A2DP to sink " + device);
+ connectSink(device);
}
break;
- case MESSAGE_DISCONNECT:
- handleDeferredDisconnect((String)msg.obj);
- break;
}
}
};
- private synchronized void onBluetoothEnable() {
- mAudioDevices = new HashMap<String, SinkState>();
- String[] paths = (String[])listHeadsetsNative();
- if (paths != null) {
- for (String path : paths) {
- mAudioDevices.put(path, new SinkState(getAddressNative(path),
- isSinkConnectedNative(path) ? BluetoothA2dp.STATE_CONNECTED :
- BluetoothA2dp.STATE_DISCONNECTED));
+ private int convertBluezSinkStringtoState(String value) {
+ if (value.equalsIgnoreCase("disconnected"))
+ return BluetoothA2dp.STATE_DISCONNECTED;
+ if (value.equalsIgnoreCase("connecting"))
+ return BluetoothA2dp.STATE_CONNECTING;
+ if (value.equalsIgnoreCase("connected"))
+ return BluetoothA2dp.STATE_CONNECTED;
+ if (value.equalsIgnoreCase("playing"))
+ return BluetoothA2dp.STATE_PLAYING;
+ return -1;
+ }
+
+ private boolean isSinkDevice(BluetoothDevice device) {
+ ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
+ if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized boolean addAudioSink (BluetoothDevice device) {
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ String propValues[] = (String []) getSinkPropertiesNative(path);
+ if (propValues == null) {
+ Log.e(TAG, "Error while getting AudioSink properties for device: " + device);
+ return false;
+ }
+ Integer state = null;
+ // Properties are name-value pairs
+ for (int i = 0; i < propValues.length; i+=2) {
+ if (propValues[i].equals(PROPERTY_STATE)) {
+ state = new Integer(convertBluezSinkStringtoState(propValues[i+1]));
+ break;
}
}
- mAudioManager.setParameter(BLUETOOTH_ENABLED, "true");
+ mAudioDevices.put(device, state);
+ handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
+ return true;
+ }
+
+ private synchronized void onBluetoothEnable() {
+ String devices = mBluetoothService.getProperty("Devices");
+ mSinkCount = 0;
+ if (devices != null) {
+ String [] paths = devices.split(",");
+ for (String path: paths) {
+ String address = mBluetoothService.getAddressFromObjectPath(path);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
+ if (remoteUuids != null)
+ if (BluetoothUuid.containsAnyUuid(remoteUuids,
+ new ParcelUuid[] {BluetoothUuid.AudioSink,
+ BluetoothUuid.AdvAudioDist})) {
+ addAudioSink(device);
+ }
+ }
+ }
+ mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
+ mAudioManager.setParameters("A2dpSuspended=false");
}
private synchronized void onBluetoothDisable() {
- if (mAudioDevices != null) {
- // copy to allow modification during iteration
- String[] paths = new String[mAudioDevices.size()];
- paths = mAudioDevices.keySet().toArray(paths);
- for (String path : paths) {
- switch (mAudioDevices.get(path).state) {
+ if (!mAudioDevices.isEmpty()) {
+ BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
+ devices = mAudioDevices.keySet().toArray(devices);
+ for (BluetoothDevice device : devices) {
+ int state = getSinkState(device);
+ switch (state) {
case BluetoothA2dp.STATE_CONNECTING:
case BluetoothA2dp.STATE_CONNECTED:
case BluetoothA2dp.STATE_PLAYING:
- disconnectSinkNative(path);
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
+ device.getAddress()));
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
break;
case BluetoothA2dp.STATE_DISCONNECTING:
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
+ BluetoothA2dp.STATE_DISCONNECTED);
break;
}
}
- mAudioDevices = null;
+ mAudioDevices.clear();
}
- mAudioManager.setBluetoothA2dpOn(false);
- mAudioManager.setParameter(BLUETOOTH_ENABLED, "false");
+
+ mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
}
- public synchronized int connectSink(String address) {
+ public synchronized boolean connectSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- if (DBG) log("connectSink(" + address + ")");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return BluetoothError.ERROR;
- }
- if (mAudioDevices == null) {
- return BluetoothError.ERROR;
- }
+ if (DBG) log("connectSink(" + device + ")");
+
// ignore if there are any active sinks
if (lookupSinksMatchingStates(new int[] {
BluetoothA2dp.STATE_CONNECTING,
BluetoothA2dp.STATE_CONNECTED,
BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
- return BluetoothError.ERROR;
+ return false;
}
- String path = lookupPath(address);
- if (path == null) {
- path = createHeadsetNative(address);
- if (DBG) log("new bluez sink: " + address + " (" + path + ")");
- }
- if (path == null) {
- return BluetoothError.ERROR;
- }
+ if (mAudioDevices.get(device) == null && !addAudioSink(device))
+ return false;
+
+ int state = mAudioDevices.get(device);
- SinkState sink = mAudioDevices.get(path);
- int state = BluetoothA2dp.STATE_DISCONNECTED;
- if (sink != null) {
- state = sink.state;
- }
switch (state) {
case BluetoothA2dp.STATE_CONNECTED:
case BluetoothA2dp.STATE_PLAYING:
case BluetoothA2dp.STATE_DISCONNECTING:
- return BluetoothError.ERROR;
+ return false;
case BluetoothA2dp.STATE_CONNECTING:
- return BluetoothError.SUCCESS;
+ return true;
}
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ if (path == null)
+ return false;
+
// State is DISCONNECTED
if (!connectSinkNative(path)) {
- return BluetoothError.ERROR;
+ return false;
}
- updateState(path, BluetoothA2dp.STATE_CONNECTING);
- return BluetoothError.SUCCESS;
+ return true;
}
- public synchronized int disconnectSink(String address) {
+ public synchronized boolean disconnectSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- if (DBG) log("disconnectSink(" + address + ")");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return BluetoothError.ERROR;
- }
- if (mAudioDevices == null) {
- return BluetoothError.ERROR;
- }
- String path = lookupPath(address);
+ if (DBG) log("disconnectSink(" + device + ")");
+
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (path == null) {
- return BluetoothError.ERROR;
+ return false;
}
- switch (mAudioDevices.get(path).state) {
+
+ switch (getSinkState(device)) {
case BluetoothA2dp.STATE_DISCONNECTED:
- return BluetoothError.ERROR;
+ return false;
case BluetoothA2dp.STATE_DISCONNECTING:
- return BluetoothError.SUCCESS;
+ return true;
}
// State is CONNECTING or CONNECTED or PLAYING
if (!disconnectSinkNative(path)) {
- return BluetoothError.ERROR;
+ return false;
} else {
- updateState(path, BluetoothA2dp.STATE_DISCONNECTING);
- return BluetoothError.SUCCESS;
+ return true;
}
}
- public synchronized List<String> listConnectedSinks() {
+ public synchronized boolean suspendSink(BluetoothDevice device) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
+ if (device == null || mAudioDevices == null) {
+ return false;
+ }
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ Integer state = mAudioDevices.get(device);
+ if (path == null || state == null) {
+ return false;
+ }
+
+ mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
+ return checkSinkSuspendState(state.intValue());
+ }
+
+ public synchronized boolean resumeSink(BluetoothDevice device) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
+ if (device == null || mAudioDevices == null) {
+ return false;
+ }
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ Integer state = mAudioDevices.get(device);
+ if (path == null || state == null) {
+ return false;
+ }
+ mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
+ return checkSinkSuspendState(state.intValue());
+ }
+
+ public synchronized BluetoothDevice[] getConnectedSinks() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return lookupSinksMatchingStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
- BluetoothA2dp.STATE_PLAYING});
+ Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
+ new int[] {BluetoothA2dp.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING});
+ return sinks.toArray(new BluetoothDevice[sinks.size()]);
}
- public synchronized int getSinkState(String address) {
+ public synchronized int getSinkState(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return BluetoothError.ERROR;
- }
- if (mAudioDevices == null) {
+ Integer state = mAudioDevices.get(device);
+ if (state == null)
return BluetoothA2dp.STATE_DISCONNECTED;
- }
- for (SinkState sink : mAudioDevices.values()) {
- if (address.equals(sink.address)) {
- return sink.state;
- }
- }
- return BluetoothA2dp.STATE_DISCONNECTED;
+ return state;
}
- public synchronized int getSinkPriority(String address) {
+ public synchronized int getSinkPriority(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return BluetoothError.ERROR;
- }
return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothA2dpSinkPriorityKey(address),
+ Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
BluetoothA2dp.PRIORITY_OFF);
}
- public synchronized int setSinkPriority(String address, int priority) {
+ public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return BluetoothError.ERROR;
+ if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
+ return false;
}
return Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothA2dpSinkPriorityKey(address), priority) ?
- BluetoothError.SUCCESS : BluetoothError.ERROR;
+ Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
}
- private synchronized void onHeadsetCreated(String path) {
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
- }
-
- private synchronized void onHeadsetRemoved(String path) {
- if (mAudioDevices == null) return;
- mAudioDevices.remove(path);
- }
-
- private synchronized void onSinkConnected(String path) {
- // if we are reconnected, do not process previous disconnect event.
- mPendingDisconnects.remove(path);
-
- if (mAudioDevices == null) return;
- // bluez 3.36 quietly disconnects the previous sink when a new sink
- // is connected, so we need to mark all previously connected sinks as
- // disconnected
-
- // copy to allow modification during iteration
- String[] paths = new String[mAudioDevices.size()];
- paths = mAudioDevices.keySet().toArray(paths);
- for (String oldPath : paths) {
- if (path.equals(oldPath)) {
- continue;
- }
- int state = mAudioDevices.get(oldPath).state;
- if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
- }
+ private synchronized void onSinkPropertyChanged(String path, String []propValues) {
+ if (!mBluetoothService.isEnabled()) {
+ return;
}
- updateState(path, BluetoothA2dp.STATE_CONNECTING);
- mAudioManager.setParameter(A2DP_SINK_ADDRESS, lookupAddress(path));
- mAudioManager.setBluetoothA2dpOn(true);
- updateState(path, BluetoothA2dp.STATE_CONNECTED);
- }
+ String name = propValues[0];
+ String address = mBluetoothService.getAddressFromObjectPath(path);
+ if (address == null) {
+ Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
+ return;
+ }
- private synchronized void onSinkDisconnected(String path) {
- // This is to work around a problem in bluez that results
- // sink disconnect events being sent, immediately followed by a reconnect.
- // To avoid unnecessary audio routing changes, we defer handling
- // sink disconnects until after a short delay.
- mPendingDisconnects.add(path);
- Message msg = Message.obtain(mHandler, MESSAGE_DISCONNECT, path);
- mHandler.sendMessageDelayed(msg, 2000);
- }
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
- private synchronized void handleDeferredDisconnect(String path) {
- if (mPendingDisconnects.contains(path)) {
- mPendingDisconnects.remove(path);
- if (mSinkCount == 1) {
- mAudioManager.setBluetoothA2dpOn(false);
+ if (name.equals(PROPERTY_STATE)) {
+ int state = convertBluezSinkStringtoState(propValues[1]);
+ if (mAudioDevices.get(device) == null) {
+ // This is for an incoming connection for a device not known to us.
+ // We have authorized it and bluez state has changed.
+ addAudioSink(device);
+ } else {
+ int prevState = mAudioDevices.get(device);
+ handleSinkStateChange(device, prevState, state);
}
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
}
}
- private synchronized void onSinkPlaying(String path) {
- updateState(path, BluetoothA2dp.STATE_PLAYING);
- }
-
- private synchronized void onSinkStopped(String path) {
- updateState(path, BluetoothA2dp.STATE_CONNECTED);
- }
-
- private synchronized final String lookupAddress(String path) {
- if (mAudioDevices == null) return null;
- SinkState sink = mAudioDevices.get(path);
- if (sink == null) {
- Log.w(TAG, "lookupAddress() called for unknown device " + path);
- updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
- }
- String address = mAudioDevices.get(path).address;
- if (address == null) Log.e(TAG, "Can't find address for " + path);
- return address;
- }
+ private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
+ if (state != prevState) {
+ if (state == BluetoothA2dp.STATE_DISCONNECTED ||
+ state == BluetoothA2dp.STATE_DISCONNECTING) {
+ if (prevState == BluetoothA2dp.STATE_CONNECTED ||
+ prevState == BluetoothA2dp.STATE_PLAYING) {
+ // disconnecting or disconnected
+ Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ mContext.sendBroadcast(intent);
+ }
+ mSinkCount--;
+ } else if (state == BluetoothA2dp.STATE_CONNECTED) {
+ mSinkCount ++;
+ }
+ mAudioDevices.put(device, state);
- private synchronized final String lookupPath(String address) {
- if (mAudioDevices == null) return null;
+ checkSinkSuspendState(state);
+ mTargetA2dpState = -1;
- for (String path : mAudioDevices.keySet()) {
- if (address.equals(mAudioDevices.get(path).address)) {
- return path;
+ if (state == BluetoothA2dp.STATE_CONNECTING) {
+ mAudioManager.setParameters("A2dpSuspended=false");
}
+ Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
+ intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+ if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
}
- return null;
}
- private synchronized List<String> lookupSinksMatchingStates(int[] states) {
- List<String> sinks = new ArrayList<String>();
- if (mAudioDevices == null) {
+ private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
+ Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
+ if (mAudioDevices.isEmpty()) {
return sinks;
}
- for (SinkState sink : mAudioDevices.values()) {
+ for (BluetoothDevice device: mAudioDevices.keySet()) {
+ int sinkState = getSinkState(device);
for (int state : states) {
- if (sink.state == state) {
- sinks.add(sink.address);
+ if (state == sinkState) {
+ sinks.add(device);
break;
}
}
@@ -441,57 +477,30 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return sinks;
}
- private synchronized void updateState(String path, int state) {
- if (mAudioDevices == null) return;
-
- SinkState s = mAudioDevices.get(path);
- int prevState;
- String address;
- if (s == null) {
- address = getAddressNative(path);
- mAudioDevices.put(path, new SinkState(address, state));
- prevState = BluetoothA2dp.STATE_DISCONNECTED;
- } else {
- address = lookupAddress(path);
- prevState = s.state;
- s.state = state;
- }
-
- if (state != prevState) {
- if (DBG) log("state " + address + " (" + path + ") " + prevState + "->" + state);
-
- // keep track of the number of active sinks
- if (prevState == BluetoothA2dp.STATE_DISCONNECTED) {
- mSinkCount++;
- } else if (state == BluetoothA2dp.STATE_DISCONNECTED) {
- mSinkCount--;
- }
-
- Intent intent = new Intent(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
- intent.putExtra(BluetoothIntent.ADDRESS, address);
- intent.putExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothA2dp.SINK_STATE, state);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-
- if ((prevState == BluetoothA2dp.STATE_CONNECTED ||
- prevState == BluetoothA2dp.STATE_PLAYING) &&
- (state != BluetoothA2dp.STATE_CONNECTING &&
- state != BluetoothA2dp.STATE_CONNECTED &&
- state != BluetoothA2dp.STATE_PLAYING)) {
- // disconnected
- intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
- mContext.sendBroadcast(intent);
+ private boolean checkSinkSuspendState(int state) {
+ boolean result = true;
+
+ if (state != mTargetA2dpState) {
+ if (state == BluetoothA2dp.STATE_PLAYING &&
+ mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
+ mAudioManager.setParameters("A2dpSuspended=true");
+ } else if (state == BluetoothA2dp.STATE_CONNECTED &&
+ mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
+ mAudioManager.setParameters("A2dpSuspended=false");
+ } else {
+ result = false;
}
}
+ return result;
}
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mAudioDevices == null) return;
+ if (mAudioDevices.isEmpty()) return;
pw.println("Cached audio devices:");
- for (String path : mAudioDevices.keySet()) {
- SinkState sink = mAudioDevices.get(path);
- pw.println(path + " " + sink.address + " " + BluetoothA2dp.stateToString(sink.state));
+ for (BluetoothDevice device : mAudioDevices.keySet()) {
+ int state = mAudioDevices.get(device);
+ pw.println(device + " " + BluetoothA2dp.stateToString(state));
}
}
@@ -501,11 +510,9 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private native boolean initNative();
private native void cleanupNative();
- private synchronized native String[] listHeadsetsNative();
- private synchronized native String createHeadsetNative(String address);
- private synchronized native boolean removeHeadsetNative(String path);
- private synchronized native String getAddressNative(String path);
private synchronized native boolean connectSinkNative(String path);
private synchronized native boolean disconnectSinkNative(String path);
- private synchronized native boolean isSinkConnectedNative(String path);
+ private synchronized native boolean suspendSinkNative(String path);
+ private synchronized native boolean resumeSinkNative(String path);
+ private synchronized native Object []getSinkPropertiesNative(String path);
}