summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--framework/java/android/bluetooth/BluetoothAdapter.java158
-rw-r--r--framework/java/android/bluetooth/BluetoothServerSocket.java14
-rw-r--r--framework/java/android/bluetooth/BluetoothSocket.java28
-rw-r--r--framework/java/android/bluetooth/BluetoothUuid.java4
-rw-r--r--framework/java/android/bluetooth/IBluetooth.aidl3
5 files changed, 186 insertions, 21 deletions
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index e3ec2cc4cb..c6a0619cf8 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -18,14 +18,19 @@ package android.bluetooth;
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 @@ import java.util.HashSet;
*/
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,29 +564,138 @@ public final class BluetoothAdapter {
}
/**
+ * 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);
+ int errno = socket.mSocket.bindListen();
+ if (errno != 0) {
+ try {
+ socket.close();
+ } 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 {
- socket.mSocket.bindListen();
- } catch (IOException e) {
+ handle = mService.addRfcommServiceRecord(name, uuid, channel, new Binder());
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ if (handle == -1) {
try {
socket.close();
- } catch (IOException e2) { }
- throw e;
+ } catch (IOException e) {}
+ throw new IOException("Not able to register SDP record for " + name);
}
+ socket.setCloseHandler(mHandler, handle);
return socket;
}
@@ -595,13 +710,12 @@ public final class BluetoothAdapter {
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 final class BluetoothAdapter {
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 @@ public final class BluetoothAdapter {
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/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java
index 45dc432d33..c14e4c0761 100644
--- a/framework/java/android/bluetooth/BluetoothServerSocket.java
+++ b/framework/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 @@ import java.io.IOException;
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 @@ public final class BluetoothServerSocket implements Closeable {
* 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/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java
index e462ea6383..573cb3d826 100644
--- a/framework/java/android/bluetooth/BluetoothSocket.java
+++ b/framework/java/android/bluetooth/BluetoothSocket.java
@@ -54,11 +54,17 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* {@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 @@ public final class BluetoothSocket implements Closeable {
*/
/*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 @@ public final class BluetoothSocket implements Closeable {
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 @@ public final class BluetoothSocket implements Closeable {
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/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java
index da0564a2a0..4164a3d6e6 100644
--- a/framework/java/android/bluetooth/BluetoothUuid.java
+++ b/framework/java/android/bluetooth/BluetoothUuid.java
@@ -50,6 +50,10 @@ public final class BluetoothUuid {
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/framework/java/android/bluetooth/IBluetooth.aidl b/framework/java/android/bluetooth/IBluetooth.aidl
index 2f77ba48da..e54abec2de 100644
--- a/framework/java/android/bluetooth/IBluetooth.aidl
+++ b/framework/java/android/bluetooth/IBluetooth.aidl
@@ -63,4 +63,7 @@ interface IBluetooth
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);
}