Provide an API for apps to use a dynamic RFCOMM channel and SDP record.

Hide listenUsingRfcommOn(int channel)
Add listenUsingRfcomm(String name, ParcelUuid uuid)

The new API automatically finds a free RFCOMM channel and registers an SDP
record with the given uuid and name. The SDP record is automatically
removed when the socket is closed, or if the application dies.

Apps are prevented from registering SDP records with the uuid of system
Bluetooth profiles, such as A2DP, HFP and OPP.

Apps are prevented from removing SDP records that they did not create. This is
tracked by pid.

TODO: Provide an API for the connecting app to look up an SDP record.

Bug: 2158900
DrNo: eastham
Joke: "What did the dog say to the tree? bark."
Change-Id: Ia92f51c34615a7270a403255ad2b8faa98c4a3f5
diff --git a/api/current.xml b/api/current.xml
index 9b0ea3a..df69ede 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -25618,7 +25618,7 @@
  visibility="public"
 >
 </method>
-<method name="listenUsingRfcommOn"
+<method name="listenUsingRfcomm"
  return="android.bluetooth.BluetoothServerSocket"
  abstract="false"
  native="false"
@@ -25628,7 +25628,9 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="channel" type="int">
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="uuid" type="android.os.ParcelUuid">
 </parameter>
 <exception name="IOException" type="java.io.IOException">
 </exception>
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e3ec2cc..c6a0619 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,14 +18,19 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Log;
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.Set;
 import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Random;
+import java.util.Set;
 
 /**
  * Represents the local Bluetooth adapter.
@@ -40,6 +45,7 @@
  */
 public final class BluetoothAdapter {
     private static final String TAG = "BluetoothAdapter";
+    private static final boolean DBG = true;  //STOPSHIP: Remove excess logging
 
     /**
      * Sentinel error value for this class. Guaranteed to not equal any other
@@ -558,33 +564,142 @@
     }
 
     /**
+     * Randomly picks RFCOMM channels until none are left.
+     * Avoids reserved channels.
+     */
+    private static class RfcommChannelPicker {
+        private static final int[] RESERVED_RFCOMM_CHANNELS =  new int[] {
+            10,  // HFAG
+            11,  // HSAG
+            12,  // OPUSH
+            19,  // PBAP
+        };
+        private static LinkedList<Integer> sChannels;  // master list of non-reserved channels
+        private static Random sRandom;
+
+        private final LinkedList<Integer> mChannels;  // local list of channels left to try
+
+        public RfcommChannelPicker() {
+            synchronized (RfcommChannelPicker.class) {
+                if (sChannels == null) {
+                    // lazy initialization of non-reserved rfcomm channels
+                    sChannels = new LinkedList<Integer>();
+                    for (int i = 1; i <= BluetoothSocket.MAX_RFCOMM_CHANNEL; i++) {
+                        sChannels.addLast(new Integer(i));
+                    }
+                    for (int reserved : RESERVED_RFCOMM_CHANNELS) {
+                        sChannels.remove(new Integer(reserved));
+                    }
+                    sRandom = new Random();
+                }
+                mChannels = (LinkedList<Integer>)sChannels.clone();
+            }
+        }
+        /* Returns next random channel, or -1 if we're out */
+        public int nextChannel() {
+            if (mChannels.size() == 0) {
+                return -1;
+            }
+            return mChannels.remove(sRandom.nextInt(mChannels.size()));
+        }
+    }
+
+    /**
      * Create a listening, secure RFCOMM Bluetooth socket.
      * <p>A remote device connecting to this socket will be authenticated and
      * communication on this socket will be encrypted.
      * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
-     * connections to listening {@link BluetoothServerSocket}.
+     * connections from a listening {@link BluetoothServerSocket}.
      * <p>Valid RFCOMM channels are in range 1 to 30.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      * @param channel RFCOMM channel to listen on
      * @return a listening RFCOMM BluetoothServerSocket
      * @throws IOException on error, for example Bluetooth not available, or
      *                     insufficient permissions, or channel in use.
+     * @hide
      */
     public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
         BluetoothServerSocket socket = new BluetoothServerSocket(
                 BluetoothSocket.TYPE_RFCOMM, true, true, channel);
-        try {
-            socket.mSocket.bindListen();
-        } catch (IOException e) {
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
             try {
                 socket.close();
-            } catch (IOException e2) { }
-            throw e;
+            } catch (IOException e) {}
+            socket.mSocket.throwErrnoNative(errno);
         }
         return socket;
     }
 
     /**
+     * Create a listening, secure RFCOMM Bluetooth socket with Service Record.
+     * <p>A remote device connecting to this socket will be authenticated and
+     * communication on this socket will be encrypted.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>The system will assign an unused RFCOMM channel to listen on.
+     * <p>The system will also register a Service Discovery
+     * Protocol (SDP) record with the local SDP server containing the specified
+     * UUID, service name, and auto-assigned channel. Remote Bluetooth devices
+     * can use the same UUID to query our SDP server and discover which channel
+     * to connect to. This SDP record will be removed when this socket is
+     * closed, or if this application closes unexpectedly.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     * @param name service name for SDP record
+     * @param uuid uuid for SDP record
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or
+     *                     insufficient permissions, or channel in use.
+     */
+    public BluetoothServerSocket listenUsingRfcomm(String name, ParcelUuid uuid)
+            throws IOException {
+        RfcommChannelPicker picker = new RfcommChannelPicker();
+
+        BluetoothServerSocket socket;
+        int channel;
+        int errno;
+        while (true) {
+            channel = picker.nextChannel();
+
+            if (channel == -1) {
+                throw new IOException("No available channels");
+            }
+
+            socket = new BluetoothServerSocket(
+                    BluetoothSocket.TYPE_RFCOMM, true, true, channel);
+            errno = socket.mSocket.bindListen();
+            if (errno == 0) {
+                if (DBG) Log.d(TAG, "listening on RFCOMM channel " + channel);
+                break;  // success
+            } else if (errno == BluetoothSocket.EADDRINUSE) {
+                if (DBG) Log.d(TAG, "RFCOMM channel " + channel + " in use");
+                try {
+                    socket.close();
+                } catch (IOException e) {}
+                continue;  // try another channel
+            } else {
+                try {
+                    socket.close();
+                } catch (IOException e) {}
+                socket.mSocket.throwErrnoNative(errno);  // Exception as a result of bindListen()
+            }
+        }
+
+        int handle = -1;
+        try {
+            handle = mService.addRfcommServiceRecord(name, uuid, channel, new Binder());
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        if (handle == -1) {
+            try {
+                socket.close();
+            } catch (IOException e) {}
+            throw new IOException("Not able to register SDP record for " + name);
+        }
+        socket.setCloseHandler(mHandler, handle);
+        return socket;
+    }
+
+    /**
      * Construct an unencrypted, unauthenticated, RFCOMM server socket.
      * Call #accept to retrieve connections to this socket.
      * @return An RFCOMM BluetoothServerSocket
@@ -595,13 +710,12 @@
     public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
         BluetoothServerSocket socket = new BluetoothServerSocket(
                 BluetoothSocket.TYPE_RFCOMM, false, false, port);
-        try {
-            socket.mSocket.bindListen();
-        } catch (IOException e) {
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
             try {
                 socket.close();
-            } catch (IOException e2) { }
-            throw e;
+            } catch (IOException e) {}
+            socket.mSocket.throwErrnoNative(errno);
         }
         return socket;
     }
@@ -617,13 +731,12 @@
     public static BluetoothServerSocket listenUsingScoOn() throws IOException {
         BluetoothServerSocket socket = new BluetoothServerSocket(
                 BluetoothSocket.TYPE_SCO, false, false, -1);
-        try {
-            socket.mSocket.bindListen();
-        } catch (IOException e) {
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
             try {
                 socket.close();
-            } catch (IOException e2) { }
-            throw e;
+            } catch (IOException e) {}
+            socket.mSocket.throwErrnoNative(errno);
         }
         return socket;
     }
@@ -636,6 +749,17 @@
         return Collections.unmodifiableSet(devices);
     }
 
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            /* handle socket closing */
+            int handle = msg.what;
+            try {
+                if (DBG) Log.d(TAG, "Removing service record " + Integer.toHexString(handle));
+                mService.removeServiceRecord(handle);
+            } catch (RemoteException e) {Log.e(TAG, "", e);}
+        }
+    };
+
     /**
      * Validate a Bluetooth address, such as "00:43:A8:23:10:F0"
      * <p>Alphabetic characters must be uppercase to be valid.
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 45dc432..c14e4c0 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import android.os.Handler;
+
 import java.io.Closeable;
 import java.io.IOException;
 
@@ -52,6 +54,8 @@
 public final class BluetoothServerSocket implements Closeable {
 
     /*package*/ final BluetoothSocket mSocket;
+    private Handler mHandler;
+    private int mMessage;
 
     /**
      * Construct a socket for incoming connections.
@@ -101,6 +105,16 @@
      * throw an IOException.
      */
     public void close() throws IOException {
+        synchronized (this) {
+            if (mHandler != null) {
+                mHandler.obtainMessage(mMessage).sendToTarget();
+            }
+        }
         mSocket.close();
     }
+
+    /*package*/ synchronized void setCloseHandler(Handler handler, int message) {
+        mHandler = handler;
+        mMessage = message;
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index e462ea6..573cb3d 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -54,11 +54,17 @@
  * {@link android.Manifest.permission#BLUETOOTH}
  */
 public final class BluetoothSocket implements Closeable {
+    /** @hide */
+    public static final int MAX_RFCOMM_CHANNEL = 30;
+
     /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
     /*package*/ static final int TYPE_RFCOMM = 1;
     /*package*/ static final int TYPE_SCO = 2;
     /*package*/ static final int TYPE_L2CAP = 3;
 
+    /*package*/ static final int EBADFD = 77;
+    /*package*/ static final int EADDRINUSE = 98;
+
     private final int mType;  /* one of TYPE_RFCOMM etc */
     private final int mPort;  /* RFCOMM channel or L2CAP psm */
     private final BluetoothDevice mDevice;    /* remote device */
@@ -90,6 +96,11 @@
      */
     /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
             BluetoothDevice device, int port) throws IOException {
+        if (type == BluetoothSocket.TYPE_RFCOMM) {
+            if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
+                throw new IOException("Invalid RFCOMM channel: " + port);
+            }
+        }
         mType = type;
         mAuth = auth;
         mEncrypt = encrypt;
@@ -211,11 +222,15 @@
         return mOutputStream;
     }
 
-    /*package*/ void bindListen() throws IOException {
+    /**
+     * Currently returns unix errno instead of throwing IOException,
+     * so that BluetoothAdapter can check the error code for EADDRINUSE
+     */
+    /*package*/ int bindListen() {
         mLock.readLock().lock();
         try {
-            if (mClosed) throw new IOException("socket closed");
-            bindListenNative();
+            if (mClosed) return EBADFD;
+            return bindListenNative();
         } finally {
             mLock.readLock().unlock();
         }
@@ -264,11 +279,16 @@
     private native void initSocketNative() throws IOException;
     private native void initSocketFromFdNative(int fd) throws IOException;
     private native void connectNative() throws IOException;
-    private native void bindListenNative() throws IOException;
+    private native int bindListenNative();
     private native BluetoothSocket acceptNative(int timeout) throws IOException;
     private native int availableNative() throws IOException;
     private native int readNative(byte[] b, int offset, int length) throws IOException;
     private native int writeNative(byte[] b, int offset, int length) throws IOException;
     private native void abortNative() throws IOException;
     private native void destroyNative() throws IOException;
+    /**
+     * Throws an IOException for given posix errno. Done natively so we can
+     * use strerr to convert to string error.
+     */
+    /*package*/ native void throwErrnoNative(int errno) throws IOException;
 }
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index da0564a..4164a3d 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -50,6 +50,10 @@
     public static final ParcelUuid ObexObjectPush =
             ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
 
+    public static final ParcelUuid[] RESERVED_UUIDS = {
+        AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
+        ObexObjectPush};
+
     public static boolean isAudioSource(ParcelUuid uuid) {
         return uuid.equals(AudioSource);
     }
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 2f77ba4..e54abec 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -63,4 +63,7 @@
 
     boolean setTrust(in String address, in boolean value);
     boolean getTrustState(in String address);
+
+    int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b);
+    void removeServiceRecord(int handle);
 }
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index edf6d10..93133d7 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -28,6 +28,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.os.ParcelUuid;
@@ -37,6 +38,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
@@ -90,6 +92,8 @@
     private final HashMap <String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
     private final ArrayList <String> mUuidIntentTracker;
 
+    private final HashMap<Integer, Integer> mServiceRecordToPid;
+
     static {
         classInitNative();
     }
@@ -117,6 +121,7 @@
 
         mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
         mUuidIntentTracker = new ArrayList<String>();
+        mServiceRecordToPid = new HashMap<Integer, Integer>();
         registerForAirplaneMode();
     }
 
@@ -206,6 +211,7 @@
 
         mIsDiscovering = false;
         mAdapterProperties.clear();
+        mServiceRecordToPid.clear();
 
         if (saveSetting) {
             persistBluetoothOnSetting(false);
@@ -1211,6 +1217,71 @@
         mDeviceServiceChannelCache.put(address, value);
     }
 
+    /**
+     * b is a handle to a Binder instance, so that this service can be notified
+     * for Applications that terminate unexpectedly, to clean there service
+     * records
+     */
+    public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid,
+            int channel, IBinder b) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        if (serviceName == null || uuid == null || channel < 1 ||
+                channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) {
+            return -1;
+        }
+        if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) {
+            Log.w(TAG, "Attempted to register a reserved UUID: " + uuid);
+            return -1;
+        }
+        int handle = addRfcommServiceRecordNative(serviceName,
+                uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
+                (short)channel);
+        if (DBG) log("new handle " + Integer.toHexString(handle));
+        if (handle == -1) {
+            return -1;
+        }
+
+        int pid = Binder.getCallingPid();
+        mServiceRecordToPid.put(new Integer(handle), new Integer(pid));
+        try {
+            b.linkToDeath(new Reaper(handle, pid), 0);
+        } catch (RemoteException e) {}
+        return handle;
+    }
+
+    public void removeServiceRecord(int handle) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        checkAndRemoveRecord(handle, Binder.getCallingPid());
+    }
+
+    private synchronized void checkAndRemoveRecord(int handle, int pid) {
+        Integer handleInt = new Integer(handle);
+        Integer owner = mServiceRecordToPid.get(handleInt);
+        if (owner != null && pid == owner.intValue()) {
+            if (DBG) log("Removing service record " + Integer.toHexString(handle) + " for pid " +
+                    pid);
+            mServiceRecordToPid.remove(handleInt);
+            removeServiceRecordNative(handle);
+        }
+    }
+
+    private class Reaper implements IBinder.DeathRecipient {
+        int pid;
+        int handle;
+        Reaper(int handle, int pid) {
+            this.pid = pid;
+            this.handle = handle;
+        }
+        public void binderDied() {
+            synchronized (BluetoothService.this) {
+                if (DBG) log("Tracked app " + pid + " died");
+                checkAndRemoveRecord(handle, pid);
+            }
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1263,25 +1334,25 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive + "\n");
-
         switch(mBluetoothState) {
         case BluetoothAdapter.STATE_OFF:
-            pw.println("\nBluetooth OFF\n");
+            pw.println("Bluetooth OFF\n");
             return;
         case BluetoothAdapter.STATE_TURNING_ON:
-            pw.println("\nBluetooth TURNING ON\n");
+            pw.println("Bluetooth TURNING ON\n");
             return;
         case BluetoothAdapter.STATE_TURNING_OFF:
-            pw.println("\nBluetooth TURNING OFF\n");
+            pw.println("Bluetooth TURNING OFF\n");
             return;
         case BluetoothAdapter.STATE_ON:
-            pw.println("\nBluetooth ON\n");
+            pw.println("Bluetooth ON\n");
         }
 
-        pw.println("\nLocal address = " + getAddress());
-        pw.println("\nLocal name = " + getName());
-        pw.println("\nisDiscovering() = " + isDiscovering());
+        pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
+
+        pw.println("Local address = " + getAddress());
+        pw.println("Local name = " + getName());
+        pw.println("isDiscovering() = " + isDiscovering());
 
         BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
 
@@ -1292,13 +1363,17 @@
                        toBondStateString(bondState),
                        mBondState.getAttempt(address),
                        getRemoteName(address));
-            if (bondState == BluetoothDevice.BOND_BONDED) {
-                ParcelUuid[] uuids = getRemoteUuids(address);
-                if (uuids == null) {
-                    pw.printf("\tuuids = null\n");
-                } else {
-                    for (ParcelUuid uuid : uuids) {
-                        pw.printf("\t" + uuid + "\n");
+
+            Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
+            if (uuidChannels == null) {
+                pw.println("\tuuids = null");
+            } else {
+                for (ParcelUuid uuid : uuidChannels.keySet()) {
+                    Integer channel = uuidChannels.get(uuid);
+                    if (channel == null) {
+                        pw.println("\t" + uuid);
+                    } else {
+                        pw.println("\t" + uuid + " RFCOMM channel = " + channel);
                     }
                 }
             }
@@ -1310,8 +1385,10 @@
             devicesObjectPath = value.split(",");
         }
         pw.println("\n--ACL connected devices--");
-        for (String device : devicesObjectPath) {
-            pw.println(getAddressFromObjectPath(device));
+        if (devicesObjectPath != null) {
+            for (String device : devicesObjectPath) {
+                pw.println(getAddressFromObjectPath(device));
+            }
         }
 
         // Rather not do this from here, but no-where else and I need this
@@ -1331,10 +1408,15 @@
             pw.println("getState() = STATE_ERROR");
             break;
         }
-        pw.println("getCurrentHeadset() = " + headset.getCurrentHeadset());
-        pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
 
+        pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset());
+        pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
         headset.close();
+        pw.println("\n--Application Service Records--");
+        for (Integer handle : mServiceRecordToPid.keySet()) {
+            Integer pid = mServiceRecordToPid.get(handle);
+            pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
+        }
     }
 
     /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
@@ -1423,8 +1505,12 @@
     private native boolean setPasskeyNative(String address, int passkey, int nativeData);
     private native boolean setPairingConfirmationNative(String address, boolean confirm,
             int nativeData);
-    private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value);
+    private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
+            int value);
     private native boolean createDeviceNative(String address);
     private native boolean discoverServicesNative(String objectPath, String pattern);
 
+    private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
+            short channel);
+    private native boolean removeServiceRecordNative(int handle);
 }
diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp
index 2532eff..31ebf8c 100644
--- a/core/jni/android_bluetooth_BluetoothSocket.cpp
+++ b/core/jni/android_bluetooth_BluetoothSocket.cpp
@@ -237,7 +237,8 @@
     jniThrowIOException(env, ENOSYS);
 }
 
-static void bindListenNative(JNIEnv *env, jobject obj) {
+/* Returns errno instead of throwing, so java can check errno */
+static int bindListenNative(JNIEnv *env, jobject obj) {
 #ifdef HAVE_BLUETOOTH
     LOGV(__FUNCTION__);
 
@@ -248,7 +249,7 @@
     struct asocket *s = get_socketData(env, obj);
 
     if (!s)
-        return;
+        return EINVAL;
 
     type = env->GetIntField(obj, field_mType);
 
@@ -283,28 +284,25 @@
         memcpy(&addr_l2.l2_bdaddr, &bdaddr, sizeof(bdaddr_t));
         break;
     default:
-        jniThrowIOException(env, ENOSYS);
-        return;
+        return ENOSYS;
     }
 
     if (bind(s->fd, addr, addr_sz)) {
         LOGV("...bind(%d) gave errno %d", s->fd, errno);
-        jniThrowIOException(env, errno);
-        return;
+        return errno;
     }
 
     if (listen(s->fd, 1)) {
         LOGV("...listen(%d) gave errno %d", s->fd, errno);
-        jniThrowIOException(env, errno);
-        return;
+        return errno;
     }
 
     LOGV("...bindListenNative(%d) success", s->fd);
 
-    return;
+    return 0;
 
 #endif
-    jniThrowIOException(env, ENOSYS);
+    return ENOSYS;
 }
 
 static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) {
@@ -521,17 +519,22 @@
     jniThrowIOException(env, ENOSYS);
 }
 
+static void throwErrnoNative(JNIEnv *env, jobject obj, jint err) {
+    jniThrowIOException(env, err);
+}
+
 static JNINativeMethod sMethods[] = {
     {"initSocketNative", "()V",  (void*) initSocketNative},
     {"initSocketFromFdNative", "(I)V",  (void*) initSocketFromFdNative},
     {"connectNative", "()V", (void *) connectNative},
-    {"bindListenNative", "()V", (void *) bindListenNative},
+    {"bindListenNative", "()I", (void *) bindListenNative},
     {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative},
     {"availableNative", "()I",    (void *) availableNative},
     {"readNative", "([BII)I",    (void *) readNative},
     {"writeNative", "([BII)I",    (void *) writeNative},
     {"abortNative", "()V",    (void *) abortNative},
     {"destroyNative", "()V",    (void *) destroyNative},
+    {"throwErrnoNative", "(I)V",    (void *) throwErrnoNative},
 };
 
 int register_android_bluetooth_BluetoothSocket(JNIEnv *env) {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index c2f93eea..ea64305 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -818,6 +818,48 @@
     return JNI_FALSE;
 }
 
+static jint addRfcommServiceRecordNative(JNIEnv *env, jobject object,
+        jstring name, jlong uuidMsb, jlong uuidLsb, jshort channel) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_name = env->GetStringUTFChars(name, NULL);
+        LOGV("... name = %s", c_name);
+        LOGV("... uuid1 = %llX", uuidMsb);
+        LOGV("... uuid2 = %llX", uuidLsb);
+        LOGV("... channel = %d", channel);
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                           get_adapter_path(env, object),
+                           DBUS_ADAPTER_IFACE, "AddRfcommServiceRecord",
+                           DBUS_TYPE_STRING, &c_name,
+                           DBUS_TYPE_UINT64, &uuidMsb,
+                           DBUS_TYPE_UINT64, &uuidLsb,
+                           DBUS_TYPE_UINT16, &channel,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(name, c_name);
+        return reply ? dbus_returns_uint32(env, reply) : -1;
+    }
+#endif
+    return -1;
+}
+
+static jboolean removeServiceRecordNative(JNIEnv *env, jobject object, jint handle) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        LOGV("... handle = %X", handle);
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                           get_adapter_path(env, object),
+                           DBUS_ADAPTER_IFACE, "RemoveServiceRecord",
+                           DBUS_TYPE_UINT32, &handle,
+                           DBUS_TYPE_INVALID);
+        return reply ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
 
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
@@ -861,6 +903,8 @@
             (void *)setDevicePropertyBooleanNative},
     {"createDeviceNative", "(Ljava/lang/String;)Z", (void *)createDeviceNative},
     {"discoverServicesNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)discoverServicesNative},
+    {"addRfcommServiceRecordNative", "(Ljava/lang/String;JJS)I", (void *)addRfcommServiceRecordNative},
+    {"removeServiceRecordNative", "(I)Z", (void *)removeServiceRecordNative},
 };
 
 int register_android_server_BluetoothService(JNIEnv *env) {