New public APIs for BluetoothA2dp and BluetoothHeadset profiles.
Change-Id: I1cc4b109542dfd62473cb95797c8c3d0d15725f4
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7e5f858..d308a5c 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -18,88 +18,104 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.server.BluetoothA2dpService;
import android.content.Context;
-import android.os.ServiceManager;
-import android.os.RemoteException;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.server.BluetoothA2dpService;
import android.util.Log;
-import java.util.Arrays;
import java.util.Collections;
-import java.util.Set;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Set;
+
/**
- * Public API for controlling the Bluetooth A2DP Profile Service.
+ * This class provides the public APIs to control the Bluetooth A2DP
+ * profile.
*
- * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
- * Service via IPC.
+ *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothA2dp proxy object.
*
- * Creating a BluetoothA2dp object will initiate a binding with the
- * BluetoothHeadset service. Users of this object should call close() when they
- * are finished, so that this proxy object can unbind from the service.
- *
- * Currently the BluetoothA2dp service runs in the system server and this
- * proxy object will be immediately bound to the service on construction.
- *
- * Currently this class provides methods to connect to A2DP audio sinks.
- *
- * @hide
+ * <p> Android only supports one connected Bluetooth A2dp device at a time.
+ * Each method is protected with its appropriate permission.
*/
-public final class BluetoothA2dp {
+public final class BluetoothA2dp implements BluetoothProfile {
private static final String TAG = "BluetoothA2dp";
private static final boolean DBG = false;
- /** int extra for ACTION_SINK_STATE_CHANGED */
- public static final String EXTRA_SINK_STATE =
- "android.bluetooth.a2dp.extra.SINK_STATE";
- /** int extra for ACTION_SINK_STATE_CHANGED */
- public static final String EXTRA_PREVIOUS_SINK_STATE =
- "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE";
-
- /** Indicates the state of an A2DP audio sink has changed.
- * This intent will always contain EXTRA_SINK_STATE,
- * EXTRA_PREVIOUS_SINK_STATE and BluetoothDevice.EXTRA_DEVICE
- * extras.
+ /**
+ * Intent used to broadcast the change in connection state of the A2DP
+ * profile.
+ *
+ * <p>This intent will have 3 extras:
+ * {@link #EXTRA_STATE} - The current state of the profile.
+ * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+ * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+ *
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_SINK_STATE_CHANGED =
- "android.bluetooth.a2dp.action.SINK_STATE_CHANGED";
+ public static final String ACTION_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
- public static final int STATE_DISCONNECTED = 0;
- public static final int STATE_CONNECTING = 1;
- public static final int STATE_CONNECTED = 2;
- public static final int STATE_DISCONNECTING = 3;
- /** Playing implies connected */
- public static final int STATE_PLAYING = 4;
+ /**
+ * Intent used to broadcast the change in the Playing state of the A2DP
+ * profile.
+ *
+ * <p>This intent will have 3 extras:
+ * {@link #EXTRA_STATE} - The current state of the profile.
+ * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+ * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+ *
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PLAYING_STATE_CHANGED =
+ "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
- /** Default priority for a2dp devices that we try to auto-connect
- * and allow incoming connections */
- public static final int PRIORITY_AUTO_CONNECT = 1000;
- /** Default priority for a2dp devices that should allow incoming
- * connections */
- public static final int PRIORITY_ON = 100;
- /** Default priority for a2dp devices that should not allow incoming
- * connections */
- public static final int PRIORITY_OFF = 0;
- /** Default priority when not set or when the device is unpaired */
- public static final int PRIORITY_UNDEFINED = -1;
+ /**
+ * A2DP sink device is streaming music. This state can be one of
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+ * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+ */
+ public static final int STATE_PLAYING = 10;
- private final IBluetoothA2dp mService;
- private final Context mContext;
+ /**
+ * A2DP sink device is NOT streaming music. This state can be one of
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+ * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+ */
+ public static final int STATE_NOT_PLAYING = 11;
+
+ private ServiceListener mServiceListener;
+ private IBluetoothA2dp mService;
+ private BluetoothAdapter mAdapter;
/**
* Create a BluetoothA2dp proxy object for interacting with the local
* Bluetooth A2DP service.
- * @param c Context
+ *
*/
- public BluetoothA2dp(Context c) {
- mContext = c;
-
+ /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) {
IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
+ mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
if (b != null) {
mService = IBluetoothA2dp.Stub.asInterface(b);
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this);
+ }
} else {
Log.w(TAG, "Bluetooth A2DP service not available!");
@@ -109,167 +125,222 @@
}
}
- /** Initiate a connection to an A2DP sink.
- * Listen for SINK_STATE_CHANGED_ACTION to find out when the
- * connection is completed.
- * @param device Remote BT device.
- * @return false on immediate error, true otherwise
- * @hide
+ /**
+ * {@inheritDoc}
+ * @hide
*/
- public boolean connectSink(BluetoothDevice device) {
- if (DBG) log("connectSink(" + device + ")");
- try {
- return mService.connectSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
+ public boolean connect(BluetoothDevice device) {
+ if (DBG) log("connect(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.connect(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
}
- /** Initiate disconnect from an A2DP sink.
- * Listen for SINK_STATE_CHANGED_ACTION to find out when
- * disconnect is completed.
- * @param device Remote BT device.
- * @return false on immediate error, true otherwise
- * @hide
+ /**
+ * {@inheritDoc}
+ * @hide
*/
- public boolean disconnectSink(BluetoothDevice device) {
- if (DBG) log("disconnectSink(" + device + ")");
- try {
- return mService.disconnectSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
+ public boolean disconnect(BluetoothDevice device) {
+ if (DBG) log("disconnect(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.disconnect(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
}
- /** Initiate suspend from an A2DP sink.
- * Listen for SINK_STATE_CHANGED_ACTION to find out when
- * suspend is completed.
- * @param device Remote BT device.
- * @return false on immediate error, true otherwise
- * @hide
+ /**
+ * {@inheritDoc}
+ */
+ public Set<BluetoothDevice> getConnectedDevices() {
+ if (DBG) log("getConnectedDevices()");
+ if (mService != null && isEnabled()) {
+ try {
+ return toDeviceSet(mService.getConnectedDevices());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) log("getDevicesMatchingStates()");
+ if (mService != null && isEnabled()) {
+ try {
+ return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getConnectionState(BluetoothDevice device) {
+ if (DBG) log("getState(" + device + ")");
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.getConnectionState(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ if (DBG) log("setPriority(" + device + ", " + priority + ")");
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ if (priority != BluetoothProfile.PRIORITY_OFF &&
+ priority != BluetoothProfile.PRIORITY_ON) {
+ return false;
+ }
+ try {
+ return mService.setPriority(device, priority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ public int getPriority(BluetoothDevice device) {
+ if (DBG) log("getPriority(" + device + ")");
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.getPriority(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.PRIORITY_OFF;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.PRIORITY_OFF;
+ }
+
+ /**
+ * Check if A2DP profile is streaming music.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device BluetoothDevice device
+ */
+ public boolean isA2dpPlaying(BluetoothDevice device) {
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.isA2dpPlaying(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Initiate suspend from an A2DP sink.
+ *
+ * <p> This API will return false in scenarios like the A2DP
+ * device is not in connected state etc. When this API returns,
+ * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
+ * intent will be broadcasted with the state. Users can get the
+ * state of the A2DP device from this intent.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param device Remote A2DP sink
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
*/
public boolean suspendSink(BluetoothDevice device) {
- try {
- return mService.suspendSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.suspendSink(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
}
- /** Initiate resume from an suspended A2DP sink.
- * Listen for SINK_STATE_CHANGED_ACTION to find out when
- * resume is completed.
- * @param device Remote BT device.
- * @return false on immediate error, true otherwise
- * @hide
+ /**
+ * Initiate resume from a suspended A2DP sink.
+ *
+ * <p> This API will return false in scenarios like the A2DP
+ * device is not in suspended state etc. When this API returns,
+ * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
+ * intent will be broadcasted with the state. Users can get the
+ * state of the A2DP device from this intent.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param device Remote A2DP sink
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
*/
public boolean resumeSink(BluetoothDevice device) {
- try {
- return mService.resumeSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
+ if (mService != null && isEnabled()
+ && isValidDevice(device)) {
+ try {
+ return mService.resumeSink(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
}
- /** Check if a specified A2DP sink is connected.
- * @param device Remote BT device.
- * @return True if connected (or playing), false otherwise and on error.
- * @hide
- */
- public boolean isSinkConnected(BluetoothDevice device) {
- if (DBG) log("isSinkConnected(" + device + ")");
- int state = getSinkState(device);
- return state == STATE_CONNECTED || state == STATE_PLAYING;
- }
-
- /** Check if any A2DP sink is connected.
- * @return a unmodifiable set of connected A2DP sinks, or null on error.
- * @hide
- */
- public Set<BluetoothDevice> getConnectedSinks() {
- if (DBG) log("getConnectedSinks()");
- try {
- return Collections.unmodifiableSet(
- new HashSet<BluetoothDevice>(Arrays.asList(mService.getConnectedSinks())));
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return null;
- }
- }
-
- /** Check if any A2DP sink is in Non Disconnected state
- * i.e playing, connected, connecting, disconnecting.
- * @return a unmodifiable set of connected A2DP sinks, or null on error.
- * @hide
- */
- public Set<BluetoothDevice> getNonDisconnectedSinks() {
- if (DBG) log("getNonDisconnectedSinks()");
- try {
- return Collections.unmodifiableSet(
- new HashSet<BluetoothDevice>(Arrays.asList(mService.getNonDisconnectedSinks())));
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return null;
- }
- }
-
- /** Get the state of an A2DP sink
- * @param device Remote BT device.
- * @return State code, one of STATE_
- * @hide
- */
- public int getSinkState(BluetoothDevice device) {
- if (DBG) log("getSinkState(" + device + ")");
- try {
- return mService.getSinkState(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return BluetoothA2dp.STATE_DISCONNECTED;
- }
- }
-
- /**
- * Set priority of a2dp sink.
- * Priority is a non-negative integer. By default paired sinks will have
- * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
- * Sinks with priority greater than zero will accept incoming connections
- * (if no sink is currently connected).
- * Priority for unpaired sink must be PRIORITY_NONE.
- * @param device Paired sink
- * @param priority Integer priority, for example PRIORITY_AUTO or
- * PRIORITY_NONE
- * @return true if priority is set, false on error
- */
- public boolean setSinkPriority(BluetoothDevice device, int priority) {
- if (DBG) log("setSinkPriority(" + device + ", " + priority + ")");
- try {
- return mService.setSinkPriority(device, priority);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
- }
- }
-
- /**
- * Get priority of a2dp sink.
- * @param device Sink
- * @return non-negative priority, or negative error code on error.
- */
- public int getSinkPriority(BluetoothDevice device) {
- if (DBG) log("getSinkPriority(" + device + ")");
- try {
- return mService.getSinkPriority(device);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return PRIORITY_OFF;
- }
- }
-
- /** Helper for converting a state to a string.
+ /**
+ * Helper for converting a state to a string.
+ *
* For debug use only - strings are not internationalized.
* @hide
*/
@@ -285,12 +356,31 @@
return "disconnecting";
case STATE_PLAYING:
return "playing";
+ case STATE_NOT_PLAYING:
+ return "not playing";
default:
return "<unknown state " + state + ">";
}
}
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+ return Collections.unmodifiableSet(
+ new HashSet<BluetoothDevice>(Arrays.asList(devices)));
+ }
+
private static void log(String msg) {
- Log.d(TAG, msg);
+ Log.d(TAG, msg);
}
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 33fd395..21a4bd6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -279,6 +280,61 @@
*/
public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME";
+ /**
+ * Intent used to broadcast the change in connection state of the local
+ * Bluetooth adapter to a profile of the remote device. When the adapter is
+ * not connected to any profiles of any remote devices and it attempts a
+ * connection to a profile this intent will sent. Once connected, this intent
+ * will not be sent for any more connection attempts to any profiles of any
+ * remote device. When the adapter disconnects from the last profile its
+ * connected to of any remote device, this intent will be sent.
+ *
+ * <p> This intent is useful for applications that are only concerned about
+ * whether the local adapter is connected to any profile of any device and
+ * are not really concerned about which profile. For example, an application
+ * which displays an icon to display whether Bluetooth is connected or not
+ * can use this intent.
+ *
+ * <p>This intent will have 3 extras:
+ * {@link #EXTRA_STATE} - The current state.
+ * {@link #EXTRA_PREVIOUS_STATE}- The previous.
+ * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+ *
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
+
+ /**
+ * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+ *
+ * This extra represents the current connection state.
+ */
+ public static final String EXTRA_CONNECTION_STATE =
+ "android.bluetooth.adapter.extra.CONNECTION_STATE";
+
+ /**
+ * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+ *
+ * This extra represents the previous connection state.
+ */
+ public static final String EXTRA_PREVIOUS_CONNECTION_STATE =
+ "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
+
+ /** The profile is in disconnected state */
+ public static final int STATE_DISCONNECTED = 0;
+ /** The profile is in connecting state */
+ public static final int STATE_CONNECTING = 1;
+ /** The profile is in connected state */
+ public static final int STATE_CONNECTED = 2;
+ /** The profile is in disconnecting state */
+ public static final int STATE_DISCONNECTING = 3;
+
/** @hide */
public static final String BLUETOOTH_SERVICE = "bluetooth";
@@ -896,6 +952,54 @@
return null;
}
+ /*
+ * Get the profile proxy object associated with the profile.
+ *
+ * <p>Profile can be one of {@link BluetoothProfile.HEADSET} or
+ * {@link BluetoothProfile.A2DP}. Clients must implements
+ * {@link BluetoothProfile.ServiceListener} to get notified of
+ * the connection status and to get the proxy object.
+ *
+ * @param context Context of the application
+ * @param listener The service Listener for connection callbacks.
+ * @param profile
+ * @return true on success, false on error
+ */
+ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
+ int profile) {
+ if (context == null || listener == null) return false;
+
+ if (profile == BluetoothProfile.HEADSET) {
+ BluetoothHeadset headset = new BluetoothHeadset(context, listener);
+ return true;
+ } else if (profile == BluetoothProfile.A2DP) {
+ BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Close the connection of the profile proxy to the Service.
+ *
+ * <p> Clients should call this when they are no longer using
+ * the proxy obtained from {@link #getProfileProxy}.
+ * Profile can be one of {@link BluetoothProfile#HEADSET} or
+ * {@link BluetoothProfile#A2DP}
+ *
+ * @param profile
+ * @param proxy Profile proxy object
+ */
+ public void closeProfileProxy(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.HEADSET) {
+ BluetoothHeadset headset = (BluetoothHeadset)proxy;
+ if (headset != null) {
+ headset.close();
+ }
+ }
+ }
+
private Set<BluetoothDevice> toDeviceSet(String[] addresses) {
Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length);
for (int i = 0; i < addresses.length; i++) {
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index be21d46..0496b1f 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -18,6 +18,8 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,63 +28,66 @@
import android.os.IBinder;
import android.util.Log;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
* Public API for controlling the Bluetooth Headset Service. This includes both
- * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
- * attempt a handsfree connection first, and fall back to headset.
+ * Bluetooth Headset and Handsfree (v1.5) profiles.
*
- * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
* Service via IPC.
*
- * Creating a BluetoothHeadset object will create a binding with the
- * BluetoothHeadset service. Users of this object should call close() when they
- * are finished with the BluetoothHeadset, so that this proxy object can unbind
- * from the service.
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHeadset proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
*
- * This BluetoothHeadset object is not immediately bound to the
- * BluetoothHeadset service. Use the ServiceListener interface to obtain a
- * notification when it is bound, this is especially important if you wish to
- * immediately call methods on BluetoothHeadset after construction.
- *
- * Android only supports one connected Bluetooth Headset at a time.
- *
- * @hide
+ * <p> Android only supports one connected Bluetooth Headset at a time.
+ * Each method is protected with its appropriate permission.
*/
-public final class BluetoothHeadset {
-
+public final class BluetoothHeadset implements BluetoothProfile {
private static final String TAG = "BluetoothHeadset";
private static final boolean DBG = false;
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_STATE_CHANGED =
- "android.bluetooth.headset.action.STATE_CHANGED";
/**
- * TODO(API release): Consider incorporating as new state in
- * HEADSET_STATE_CHANGED
+ * Intent used to broadcast the change in connection state of the Headset
+ * profile.
+ *
+ * <p>This intent will have 3 extras:
+ * {@link #EXTRA_STATE} - The current state of the profile.
+ * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+ * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+ *
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
+
+ /**
+ * Intent used to broadcast the change in the Audio Connection state of the
+ * A2DP profile.
+ *
+ * <p>This intent will have 3 extras:
+ * {@link #EXTRA_STATE} - The current state of the profile.
+ * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
+ * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+ *
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_AUDIO_STATE_CHANGED =
- "android.bluetooth.headset.action.AUDIO_STATE_CHANGED";
- public static final String EXTRA_STATE =
- "android.bluetooth.headset.extra.STATE";
- public static final String EXTRA_PREVIOUS_STATE =
- "android.bluetooth.headset.extra.PREVIOUS_STATE";
- public static final String EXTRA_AUDIO_STATE =
- "android.bluetooth.headset.extra.AUDIO_STATE";
+ "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
- /** Extra to be used with the Headset State change intent.
- * This will be used only when Headset state changes to
- * {@link #STATE_DISCONNECTED} from any previous state.
- * This extra field is optional and will be used when
- * we have deterministic information regarding whether
- * the disconnect was initiated by the remote device or
- * by the local adapter.
- */
- public static final String EXTRA_DISCONNECT_INITIATOR =
- "android.bluetooth.headset.extra.DISCONNECT_INITIATOR";
/**
* Broadcast Action: Indicates a headset has posted a vendor-specific event.
@@ -124,100 +129,47 @@
public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
"android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
+ /*
+ * Headset state when SCO audio is connected
+ * This state can be one of
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+ * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
+ */
+ public static final int STATE_AUDIO_CONNECTED = 10;
/**
- * TODO(API release): Consider incorporating as new state in
- * HEADSET_STATE_CHANGED
+ * Headset state when SCO audio is NOT connected
+ * This state can be one of
+ * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+ * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
*/
+ public static final int STATE_AUDIO_DISCONNECTED = 11;
+
+
+ private Context mContext;
+ private ServiceListener mServiceListener;
private IBluetoothHeadset mService;
- private final Context mContext;
- private final ServiceListener mServiceListener;
-
- /** There was an error trying to obtain the state */
- public static final int STATE_ERROR = -1;
- /** No headset currently connected */
- public static final int STATE_DISCONNECTED = 0;
- /** Connection attempt in progress */
- public static final int STATE_CONNECTING = 1;
- /** A headset is currently connected */
- public static final int STATE_CONNECTED = 2;
-
- /** A SCO audio channel is not established */
- public static final int AUDIO_STATE_DISCONNECTED = 0;
- /** A SCO audio channel is established */
- public static final int AUDIO_STATE_CONNECTED = 1;
-
- public static final int RESULT_FAILURE = 0;
- public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
- public static final int RESULT_CANCELED = 2;
-
- /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
- public static final int REMOTE_DISCONNECT = 0;
- public static final int LOCAL_DISCONNECT = 1;
-
-
- /** Default priority for headsets that for which we will accept
- * inconing connections and auto-connect */
- public static final int PRIORITY_AUTO_CONNECT = 1000;
- /** Default priority for headsets that for which we will accept
- * inconing connections but not auto-connect */
- public static final int PRIORITY_ON = 100;
- /** Default priority for headsets that should not be auto-connected
- * and not allow incoming connections. */
- public static final int PRIORITY_OFF = 0;
- /** Default priority when not set or when the device is unpaired */
- public static final int PRIORITY_UNDEFINED = -1;
-
- /**
- * An interface for notifying BluetoothHeadset IPC clients when they have
- * been connected to the BluetoothHeadset service.
- */
- public interface ServiceListener {
- /**
- * Called to notify the client when this proxy object has been
- * connected to the BluetoothHeadset service. Clients must wait for
- * this callback before making IPC calls on the BluetoothHeadset
- * service.
- */
- public void onServiceConnected();
-
- /**
- * Called to notify the client that this proxy object has been
- * disconnected from the BluetoothHeadset service. Clients must not
- * make IPC calls on the BluetoothHeadset service after this callback.
- * This callback will currently only occur if the application hosting
- * the BluetoothHeadset service, but may be called more often in future.
- */
- public void onServiceDisconnected();
- }
+ BluetoothAdapter mAdapter;
/**
* Create a BluetoothHeadset proxy object.
*/
- public BluetoothHeadset(Context context, ServiceListener l) {
+ /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
mContext = context;
mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Headset Service");
}
}
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-
/**
* Close the connection to the backing service.
* Other public functions of BluetoothHeadset will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
*/
- public synchronized void close() {
+ /*package*/ synchronized void close() {
if (DBG) log("close()");
if (mConnection != null) {
mContext.unbindService(mConnection);
@@ -226,190 +178,212 @@
}
/**
- * Get the current state of the Bluetooth Headset service.
- * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
- * object is currently not connected to the Headset service.
+ * {@inheritDoc}
+ * @hide
*/
- public int getState(BluetoothDevice device) {
- if (DBG) log("getState()");
- if (mService != null) {
+ public boolean connect(BluetoothDevice device) {
+ if (DBG) log("connect(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
try {
- return mService.getState(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return mService.connect(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
- return BluetoothHeadset.STATE_ERROR;
- }
-
- /**
- * Get the BluetoothDevice for the current headset.
- * @return current headset, or null if not in connected or connecting
- * state, or if this proxy object is not connected to the Headset
- * service.
- */
- public BluetoothDevice getCurrentHeadset() {
- if (DBG) log("getCurrentHeadset()");
- if (mService != null) {
- try {
- return mService.getCurrentHeadset();
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return null;
- }
-
- /**
- * Request to initiate a connection to a headset.
- * This call does not block. Fails if a headset is already connecting
- * or connected.
- * Initiates auto-connection if device is null. Tries to connect to all
- * devices with priority greater than PRIORITY_AUTO in descending order.
- * @param device device to connect to, or null to auto-connect last connected
- * headset
- * @return false if there was a problem initiating the connection
- * procedure, and no further HEADSET_STATE_CHANGED intents
- * will be expected.
- */
- public boolean connectHeadset(BluetoothDevice device) {
- if (DBG) log("connectHeadset(" + device + ")");
- if (mService != null) {
- try {
- if (mService.connectHeadset(device)) {
- return true;
- }
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
/**
- * Returns true if the specified headset is connected (does not include
- * connecting). Returns false if not connected, or if this proxy object
- * if not currently connected to the headset service.
+ * {@inheritDoc}
+ * @hide
*/
- public boolean isConnected(BluetoothDevice device) {
- if (DBG) log("isConnected(" + device + ")");
- if (mService != null) {
+ public boolean disconnect(BluetoothDevice device) {
+ if (DBG) log("disconnect(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
try {
- return mService.isConnected(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return mService.disconnect(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
/**
- * Disconnects the current headset. Currently this call blocks, it may soon
- * be made asynchornous. Returns false if this proxy object is
- * not currently connected to the Headset service.
+ * {@inheritDoc}
*/
- public boolean disconnectHeadset(BluetoothDevice device) {
- if (DBG) log("disconnectHeadset()");
- if (mService != null) {
+ public Set<BluetoothDevice> getConnectedDevices() {
+ if (DBG) log("getConnectedDevices()");
+ if (mService != null && isEnabled()) {
try {
- mService.disconnectHeadset(device);
- return true;
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(mService.getConnectedDevices());
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
}
- return false;
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return toDeviceSet(new BluetoothDevice[0]);
}
/**
- * Start BT Voice Recognition mode, and set up Bluetooth audio path.
- * Returns false if there is no headset connected, or if the
- * connected headset does not support voice recognition, or on
- * error.
+ * {@inheritDoc}
*/
- public boolean startVoiceRecognition() {
- if (DBG) log("startVoiceRecognition()");
- if (mService != null) {
+ public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) log("getDevicesMatchingStates()");
+ if (mService != null && isEnabled()) {
try {
- return mService.startVoiceRecognition();
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return toDeviceSet(new BluetoothDevice[0]);
+ }
}
- return false;
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return toDeviceSet(new BluetoothDevice[0]);
}
/**
- * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
- * Returns false if there is no headset connected, or the connected
- * headset is not in voice recognition mode, or on error.
+ * {@inheritDoc}
*/
- public boolean stopVoiceRecognition() {
- if (DBG) log("stopVoiceRecognition()");
- if (mService != null) {
+ public int getConnectionState(BluetoothDevice device) {
+ if (DBG) log("getConnectionState(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
try {
- return mService.stopVoiceRecognition();
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return mService.getConnectionState(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
}
- return false;
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.STATE_DISCONNECTED;
}
/**
- * Set priority of headset.
- * Priority is a non-negative integer. By default paired headsets will have
- * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
- * Headsets with priority greater than zero will be auto-connected, and
- * incoming connections will be accepted (if no other headset is
- * connected).
- * Auto-connection occurs at the following events: boot, incoming phone
- * call, outgoing phone call.
- * Headsets with priority equal to zero, or that are unpaired, are not
- * auto-connected.
- * Incoming connections are ignored regardless of priority if there is
- * already a headset connected.
- * @param device paired headset
- * @param priority Integer priority, for example PRIORITY_AUTO or
- * PRIORITY_NONE
- * @return true if successful, false if there was some error
+ * {@inheritDoc}
+ * @hide
*/
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
- if (mService != null) {
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ if (priority != BluetoothProfile.PRIORITY_OFF &&
+ priority != BluetoothProfile.PRIORITY_ON) {
+ return false;
+ }
try {
return mService.setPriority(device, priority);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
/**
- * Get priority of headset.
- * @param device headset
- * @return non-negative priority, or negative error code on error
+ * {@inheritDoc}
+ * @hide
*/
public int getPriority(BluetoothDevice device) {
if (DBG) log("getPriority(" + device + ")");
- if (mService != null) {
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
try {
return mService.getPriority(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return PRIORITY_OFF;
+ }
}
- return -1;
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return PRIORITY_OFF;
+ }
+
+ /**
+ * Start Bluetooth voice recognition. This methods sends the voice
+ * recognition AT command to the headset and establishes the
+ * audio connection.
+ *
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED}
+ * when the audio connection is established.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device Bluetooth headset
+ * @return false if there is no headset connected of if the
+ * connected headset doesn't support voice recognition
+ * or on error, true otherwise
+ */
+ public boolean startVoiceRecognition(BluetoothDevice device) {
+ if (DBG) log("startVoiceRecognition()");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.startVoiceRecognition(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Stop Bluetooth Voice Recognition mode, and shut down the
+ * Bluetooth audio path.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device Bluetooth headset
+ * @return false if there is no headset connected
+ * or on error, true otherwise
+ */
+ public boolean stopVoiceRecognition(BluetoothDevice device) {
+ if (DBG) log("stopVoiceRecognition()");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.stopVoiceRecognition(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Check if Bluetooth SCO audio is connected.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device Bluetooth headset
+ * @return true if SCO is connected,
+ * false otherwise or on error
+ */
+ public boolean isAudioConnected(BluetoothDevice device) {
+ if (DBG) log("isAudioConnected()");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.isAudioConnected(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
}
/**
@@ -420,24 +394,29 @@
* boot. This is a good indicator for spammy headset/handsfree units that
* can keep the device awake by polling for cellular status updates. As a
* rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
+ *
+ * @param device the bluetooth headset.
* @return monotonically increasing battery usage hint, or a negative error
* code on error
* @hide
*/
- public int getBatteryUsageHint() {
+ public int getBatteryUsageHint(BluetoothDevice device) {
if (DBG) log("getBatteryUsageHint()");
- if (mService != null) {
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
try {
- return mService.getBatteryUsageHint();
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return mService.getBatteryUsageHint(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
return -1;
}
+
/**
* Indicates if current platform supports voice dialing over bluetooth SCO.
+ *
* @return true if voice dialing over bluetooth is supported, false otherwise.
* @hide
*/
@@ -448,11 +427,13 @@
/**
* Cancel the outgoing connection.
+ * Note: This is an internal function and shouldn't be exposed
+ *
* @hide
*/
public boolean cancelConnectThread() {
if (DBG) log("cancelConnectThread");
- if (mService != null) {
+ if (mService != null && isEnabled()) {
try {
return mService.cancelConnectThread();
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -465,11 +446,13 @@
/**
* Accept the incoming connection.
+ * Note: This is an internal function and shouldn't be exposed
+ *
* @hide
*/
public boolean acceptIncomingConnect(BluetoothDevice device) {
if (DBG) log("acceptIncomingConnect");
- if (mService != null) {
+ if (mService != null && isEnabled()) {
try {
return mService.acceptIncomingConnect(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -481,12 +464,14 @@
}
/**
- * Create the connect thread the incoming connection.
+ * Create the connect thread for the incoming connection.
+ * Note: This is an internal function and shouldn't be exposed
+ *
* @hide
*/
public boolean createIncomingConnect(BluetoothDevice device) {
if (DBG) log("createIncomingConnect");
- if (mService != null) {
+ if (mService != null && isEnabled()) {
try {
return mService.createIncomingConnect(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -500,11 +485,12 @@
/**
* Connect to a Bluetooth Headset.
* Note: This is an internal function and shouldn't be exposed
+ *
* @hide
*/
public boolean connectHeadsetInternal(BluetoothDevice device) {
if (DBG) log("connectHeadsetInternal");
- if (mService != null) {
+ if (mService != null && isEnabled()) {
try {
return mService.connectHeadsetInternal(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -518,11 +504,12 @@
/**
* Disconnect a Bluetooth Headset.
* Note: This is an internal function and shouldn't be exposed
+ *
* @hide
*/
public boolean disconnectHeadsetInternal(BluetoothDevice device) {
if (DBG) log("disconnectHeadsetInternal");
- if (mService != null) {
+ if (mService != null && isEnabled()) {
try {
return mService.disconnectHeadsetInternal(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -532,23 +519,61 @@
}
return false;
}
+
+ /**
+ * Set the audio state of the Headset.
+ * Note: This is an internal function and shouldn't be exposed
+ *
+ * @hide
+ */
+ public boolean setAudioState(BluetoothDevice device, int state) {
+ if (DBG) log("setAudioState");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.setAudioState(device, state);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
mService = IBluetoothHeadset.Stub.asInterface(service);
+
if (mServiceListener != null) {
- mServiceListener.onServiceConnected();
+ mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
}
}
public void onServiceDisconnected(ComponentName className) {
if (DBG) Log.d(TAG, "Proxy object disconnected");
mService = null;
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected();
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
}
}
};
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+ return Collections.unmodifiableSet(
+ new HashSet<BluetoothDevice>(Arrays.asList(devices)));
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
new file mode 100644
index 0000000..3b4c84c
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+
+package android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
+import java.util.Set;
+
+/**
+ * Public APIs for the Bluetooth Profiles.
+ *
+ * <p> Clients should call {@link BluetoothAdapter#getProfileProxy},
+ * to get the Profile Proxy. Each public profile implements this
+ * interface.
+ */
+public interface BluetoothProfile {
+
+ /**
+ * Extra for the connection state intents of the individual profiles.
+ *
+ * This extra represents the current connection state of the profile of the
+ * Bluetooth device.
+ */
+ public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
+
+ /**
+ * Extra for the connection state intents of the individual profiles.
+ *
+ * This extra represents the previous connection state of the profile of the
+ * Bluetooth device.
+ */
+ public static final String EXTRA_PREVIOUS_STATE =
+ "android.bluetooth.profile.extra.PREVIOUS_STATE";
+
+ /** The profile is in disconnected state */
+ public static final int STATE_DISCONNECTED = 0;
+ /** The profile is in connecting state */
+ public static final int STATE_CONNECTING = 1;
+ /** The profile is in connected state */
+ public static final int STATE_CONNECTED = 2;
+ /** The profile is in disconnecting state */
+ public static final int STATE_DISCONNECTING = 3;
+
+ /**
+ * Headset and Handsfree profile
+ */
+ public static final int HEADSET = 1;
+ /**
+ * A2DP profile.
+ */
+ public static final int A2DP = 2;
+
+ /**
+ * Default priority for devices that we try to auto-connect to and
+ * and allow incoming connections for the profile
+ * @hide
+ **/
+ public static final int PRIORITY_AUTO_CONNECT = 1000;
+
+ /**
+ * Default priority for devices that allow incoming
+ * and outgoing connections for the profile
+ * @hide
+ **/
+ public static final int PRIORITY_ON = 100;
+
+ /**
+ * Default priority for devices that does not allow incoming
+ * connections and outgoing connections for the profile.
+ * @hide
+ **/
+ public static final int PRIORITY_OFF = 0;
+
+ /**
+ * Default priority when not set or when the device is unpaired
+ * @hide
+ * */
+ public static final int PRIORITY_UNDEFINED = -1;
+
+ /**
+ * Initiate connection to a profile of the remote bluetooth device.
+ *
+ * <p> Currently, the system supports only 1 connection to the
+ * A2DP and Headset/Handsfree profile. The API will automatically
+ * disconnect connected devices before connecting.
+ *
+ * <p> This API returns false in scenarios like the profile on the
+ * device is already connected or Bluetooth is not turned on.
+ * When this API returns true, it is guaranteed that
+ * connection state intent for the profile will be broadcasted with
+ * the state. Users can get the connection state of the profile
+ * from this intent.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean connect(BluetoothDevice device);
+
+ /**
+ * Initiate disconnection from a profile
+ *
+ * <p> This API will return false in scenarios like the profile on the
+ * Bluetooth device is not in connected state etc. When this API returns,
+ * true, it is guaranteed that the connection state change
+ * intent will be broadcasted with the state. Users can get the
+ * disconnection state of the profile from this intent.
+ *
+ * <p> If the disconnection is initiated by a remote device, the state
+ * will transition from {@link #STATE_CONNECTED} to
+ * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+ * host (local) device the state will transition from
+ * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+ * state {@link #STATE_DISCONNECTED}. The transition to
+ * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+ * two scenarios.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean disconnect(BluetoothDevice device);
+
+ /**
+ * Get connected devices for this specific profile.
+ *
+ * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @return An unmodifiable set of devices. The set will be empty on error.
+ */
+ public Set<BluetoothDevice> getConnectedDevices();
+
+ /**
+ * Get a set of devices that match any of the given connection
+ * states.
+ *
+ * <p> If none of devices match any of the given states,
+ * an empty set will be returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param states Array of states. States can be one of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+ * @return An unmodifiable set of devices. The set will be empty on error.
+ */
+ public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
+
+ /**
+ * Get the current connection state of the profile
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device Remote bluetooth device.
+ * @return State of the profile connection. One of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+ */
+ public int getConnectionState(BluetoothDevice device);
+
+ /**
+ * Set priority of the profile
+ *
+ * <p> The device should already be paired.
+ * Priority can be one of {@link #PRIORITY_ON} or
+ * {@link #PRIORITY_OFF},
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param device Paired bluetooth device
+ * @param priority
+ * @return true if priority is set, false on error
+ * @hide
+ */
+ public boolean setPriority(BluetoothDevice device, int priority);
+
+ /**
+ * Get the priority of the profile.
+ *
+ * <p> The priority can be any of:
+ * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+ * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param device Bluetooth device
+ * @return priority of the device
+ * @hide
+ */
+ public int getPriority(BluetoothDevice device);
+
+ /**
+ * An interface for notifying BluetoothProfile IPC clients when they have
+ * been connected or disconnected to the service.
+ */
+ public interface ServiceListener {
+ /**
+ * Called to notify the client when the proxy object has been
+ * connected to the service.
+ * @param profile - One of {@link #HEADSET} or
+ * {@link #A2DP}
+ * @param proxy - One of {@link BluetoothHeadset} or
+ * {@link BluetoothA2dp}
+ */
+ public void onServiceConnected(int profile, BluetoothProfile proxy);
+
+ /**
+ * Called to notify the client that this proxy object has been
+ * disconnected from the service.
+ * @param profile - One of {@link #HEADSET} or
+ * {@link #A2DP}
+ */
+ public void onServiceDisconnected(int profile);
+ }
+}