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);
+    }
+}