diff options
| author | 2010-09-28 19:56:05 -0700 | |
|---|---|---|
| committer | 2010-09-28 19:56:05 -0700 | |
| commit | c9cc487b7ba38240d5348084b4c04d5b9ed240ec (patch) | |
| tree | 40208c07240fd8fa16e47864c34a5b47f03dbe66 | |
| parent | dcf2be6cf660269c77f51ff0e0f336726d1625c6 (diff) | |
| parent | 62c37efc9e894809b29a004c142a8e0a6b374db7 (diff) | |
Merge "New public APIs for BluetoothA2dp and BluetoothHeadset profiles."
| -rw-r--r-- | api/current.xml | 514 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothA2dp.java | 436 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothAdapter.java | 104 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothHeadset.java | 545 | ||||
| -rw-r--r-- | core/java/android/bluetooth/BluetoothProfile.java | 239 |
5 files changed, 1405 insertions, 433 deletions
diff --git a/api/current.xml b/api/current.xml index 7e7131b0f474..e3b6a01ea09a 100644 --- a/api/current.xml +++ b/api/current.xml @@ -38675,6 +38675,111 @@ </package> <package name="android.bluetooth" > +<class name="BluetoothA2dp" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.bluetooth.BluetoothProfile"> +</implements> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<method name="isA2dpPlaying" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_PLAYING_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_NOT_PLAYING" + type="int" + transient="false" + volatile="false" + value="11" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_PLAYING" + type="int" + transient="false" + volatile="false" + value="10" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="BluetoothAdapter" extends="java.lang.Object" abstract="false" @@ -38707,6 +38812,21 @@ <parameter name="address" type="java.lang.String"> </parameter> </method> +<method name="closeProfileProxy" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +<parameter name="proxy" type="android.bluetooth.BluetoothProfile"> +</parameter> +</method> <method name="disable" return="boolean" abstract="false" @@ -38773,6 +38893,23 @@ visibility="public" > </method> +<method name="getProfileProxy" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="listener" type="android.bluetooth.BluetoothProfile.ServiceListener"> +</parameter> +<parameter name="profile" type="int"> +</parameter> +</method> <method name="getRemoteDevice" return="android.bluetooth.BluetoothDevice" abstract="false" @@ -38871,6 +39008,17 @@ visibility="public" > </method> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_DISCOVERY_FINISHED" type="java.lang.String" transient="false" @@ -38959,6 +39107,17 @@ visibility="public" > </field> +<field name="EXTRA_CONNECTION_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.extra.CONNECTION_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="EXTRA_DISCOVERABLE_DURATION" type="java.lang.String" transient="false" @@ -38981,6 +39140,17 @@ visibility="public" > </field> +<field name="EXTRA_PREVIOUS_CONNECTION_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="EXTRA_PREVIOUS_SCAN_MODE" type="java.lang.String" transient="false" @@ -39058,6 +39228,50 @@ visibility="public" > </field> +<field name="STATE_CONNECTED" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTING" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="STATE_OFF" type="int" transient="false" @@ -40299,6 +40513,306 @@ > </field> </class> +<class name="BluetoothHeadset" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.bluetooth.BluetoothProfile"> +</implements> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<method name="isAudioConnected" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="startVoiceRecognition" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="stopVoiceRecognition" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<field name="ACTION_AUDIO_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_AUDIO_CONNECTED" + type="int" + transient="false" + volatile="false" + value="10" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_AUDIO_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="11" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<interface name="BluetoothProfile" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<field name="A2DP" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_PREVIOUS_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.profile.extra.PREVIOUS_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.profile.extra.STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="HEADSET" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTED" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTING" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</interface> +<interface name="BluetoothProfile.ServiceListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onServiceConnected" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +<parameter name="proxy" type="android.bluetooth.BluetoothProfile"> +</parameter> +</method> +<method name="onServiceDisconnected" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +</method> +</interface> <class name="BluetoothServerSocket" extends="java.lang.Object" abstract="false" diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7e5f858f050b..d308a5c315bd 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -18,88 +18,104 @@ package android.bluetooth; 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. - * - * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP - * Service via IPC. + * This class provides the public APIs to control the Bluetooth A2DP + * profile. * - * 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. + *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothA2dp proxy object. * - * 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"; + /** + * 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_CONNECTION_STATE_CHANGED = + "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; - /** 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 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_SINK_STATE_CHANGED = - "android.bluetooth.a2dp.action.SINK_STATE_CHANGED"; + public static final String ACTION_PLAYING_STATE_CHANGED = + "android.bluetooth.a2dp.profile.action.PLAYING_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; + /** + * 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; - /** 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 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 final IBluetoothA2dp mService; - private final Context mContext; + 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 @@ public final class BluetoothA2dp { } } - /** 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 boolean suspendSink(BluetoothDevice device) { - try { - return mService.suspendSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + 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]); } - /** 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 + /** + * {@inheritDoc} */ - public boolean resumeSink(BluetoothDevice device) { - try { - return mService.resumeSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + 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]); } - /** 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 + /** + * {@inheritDoc} */ - public boolean isSinkConnected(BluetoothDevice device) { - if (DBG) log("isSinkConnected(" + device + ")"); - int state = getSinkState(device); - return state == STATE_CONNECTED || state == STATE_PLAYING; + 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; } - /** Check if any A2DP sink is connected. - * @return a unmodifiable set of connected A2DP sinks, or null on error. + /** + * {@inheritDoc} * @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; + 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; } - /** 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. + /** + * {@inheritDoc} * @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; + 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; } - /** Get the state of an A2DP sink - * @param device Remote BT device. - * @return State code, one of STATE_ - * @hide + /** + * Check if A2DP profile is streaming music. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device BluetoothDevice device */ - 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; + 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; } /** - * 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 + * 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 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; + public boolean suspendSink(BluetoothDevice device) { + 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; } /** - * Get priority of a2dp sink. - * @param device Sink - * @return non-negative priority, or negative error code on error. + * 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 int getSinkPriority(BluetoothDevice device) { - if (DBG) log("getSinkPriority(" + device + ")"); - try { - return mService.getSinkPriority(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return PRIORITY_OFF; + public boolean resumeSink(BluetoothDevice device) { + 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; } - /** 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 @@ public final class BluetoothA2dp { 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 33fd39513f52..21a4bd64b4d5 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,7 @@ package android.bluetooth; 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 final class BluetoothAdapter { */ 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 @@ public final class BluetoothAdapter { 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 be21d46430d4..0496b1f27cef 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -18,6 +18,8 @@ package android.bluetooth; 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.RemoteException; 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. - * - * 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. + * <p> Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothHeadset proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. * - * 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; + /** + * 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_STATE_CHANGED = - "android.bluetooth.headset.action.STATE_CHANGED"; + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; + /** - * TODO(API release): Consider incorporating as new state in - * HEADSET_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"; - - /** 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"; + "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; + /** * Broadcast Action: Indicates a headset has posted a vendor-specific event. @@ -124,100 +129,47 @@ public final class BluetoothHeadset { public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; - - /** - * TODO(API release): Consider incorporating as new state in - * HEADSET_STATE_CHANGED + /* + * 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. */ - 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; + public static final int STATE_AUDIO_CONNECTED = 10; /** - * An interface for notifying BluetoothHeadset IPC clients when they have - * been connected to the BluetoothHeadset service. + * 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 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(); - } + public static final int STATE_AUDIO_DISCONNECTED = 11; + + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothHeadset mService; + 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 @@ public final class BluetoothHeadset { } /** - * 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; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * 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. + * {@inheritDoc} + * @hide */ - public BluetoothDevice getCurrentHeadset() { - if (DBG) log("getCurrentHeadset()"); - if (mService != null) { + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { 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 mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } } - return null; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * 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. + * {@inheritDoc} */ - public boolean connectHeadset(BluetoothDevice device) { - if (DBG) log("connectHeadset(" + device + ")"); - if (mService != null) { + public Set<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { 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())); + 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]); } /** - * 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} */ - public boolean isConnected(BluetoothDevice device) { - if (DBG) log("isConnected(" + device + ")"); - if (mService != null) { + public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { 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 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]); } /** - * 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 int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { 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 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; } /** - * 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} + * @hide */ - public boolean startVoiceRecognition() { - if (DBG) log("startVoiceRecognition()"); - if (mService != null) { + 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.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 mService.setPriority(device, priority); + } 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; } /** - * 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} + * @hide */ - public boolean stopVoiceRecognition() { - if (DBG) log("stopVoiceRecognition()"); - if (mService != null) { + public int getPriority(BluetoothDevice device) { + if (DBG) log("getPriority(" + 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.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } } + 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; } /** - * 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 + * 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 setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null) { + public boolean stopVoiceRecognition(BluetoothDevice device) { + if (DBG) log("stopVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { 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())); + 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; } /** - * Get priority of headset. - * @param device headset - * @return non-negative priority, or negative error code on error + * 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 int getPriority(BluetoothDevice device) { - if (DBG) log("getPriority(" + device + ")"); - if (mService != null) { + public boolean isAudioConnected(BluetoothDevice device) { + if (DBG) log("isAudioConnected()"); + 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())); + return mService.isAudioConnected(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } } - return -1; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** @@ -420,24 +394,29 @@ public final class BluetoothHeadset { * 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 @@ public final class BluetoothHeadset { /** * 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 @@ public final class BluetoothHeadset { /** * 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 @@ public final class BluetoothHeadset { } /** - * 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 @@ public final class BluetoothHeadset { /** * 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 @@ public final class BluetoothHeadset { /** * 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 @@ public final class BluetoothHeadset { } 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 000000000000..3b4c84c9ea5a --- /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); + } +} |