Encourage developers to connect RFCOMM by UUID instead of Channel.
Hide createRfcommSocket(int channel)
Add createRfcommSocketWithServiceRecord(UUID uuid)
Rename listenUsingRfcomm(String,UUID) -> listenUsingRfcommWithServiceRecord(..)
Now we have a complete API for developers to make peer-peer RFCOMM connections
with hard-coding the limited (30) RFCOMM channels, instead using SDP lookup
of an UUID.
This commit addresses two serious bugs:
- Do not throw IOException on accepting an incoming RFCOMM connection with
BluetoothSocket. This was a regression from commit 24bb9b8af4ff6915
- Workaround failure of bluez to update SDP cache when channel changes by
trying to use the same RFCOMM channel on the server every time, instead
of picking server channels randomly. This is a pretty ugly workaround,
and we are still trying to fix the caching issue - but with this
workaround we are at least shippable and apps will work at least until
they start colliding on the 30 RFCOMM channels.
DrNo: eastham
Bug: 2158900
Joke: What did the digital watch say to his mom? "Look mom no hands."
Change-Id: Ia4879943b83afac06b6f1a3f2391cf1628afce7d
diff --git a/Android.mk b/Android.mk
index 1428454..2e2fec1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -90,6 +90,7 @@
core/java/android/backup/IRestoreSession.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
+ core/java/android/bluetooth/IBluetoothCallback.aidl \
core/java/android/bluetooth/IBluetoothHeadset.aidl \
core/java/android/bluetooth/IBluetoothPbap.aidl \
core/java/android/content/IContentService.aidl \
diff --git a/api/current.xml b/api/current.xml
index e0a0278..0bca84b 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -25618,7 +25618,7 @@
visibility="public"
>
</method>
-<method name="listenUsingRfcomm"
+<method name="listenUsingRfcommWithServiceRecord"
return="android.bluetooth.BluetoothServerSocket"
abstract="false"
native="false"
@@ -25630,7 +25630,7 @@
>
<parameter name="name" type="java.lang.String">
</parameter>
-<parameter name="uuid" type="android.os.ParcelUuid">
+<parameter name="uuid" type="java.util.UUID">
</parameter>
<exception name="IOException" type="java.io.IOException">
</exception>
@@ -26804,7 +26804,7 @@
>
<implements name="android.os.Parcelable">
</implements>
-<method name="createRfcommSocket"
+<method name="createRfcommSocketToServiceRecord"
return="android.bluetooth.BluetoothSocket"
abstract="false"
native="false"
@@ -26814,7 +26814,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="channel" type="int">
+<parameter name="uuid" type="java.util.UUID">
</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 c6a0619..8ce911d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -31,6 +31,7 @@
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;
+import java.util.UUID;
/**
* Represents the local Bluetooth adapter.
@@ -564,8 +565,16 @@
}
/**
- * Randomly picks RFCOMM channels until none are left.
+ * Picks RFCOMM channels until none are left.
* Avoids reserved channels.
+ * Ideally we would pick random channels, but in the current implementation
+ * we start with the channel that is the hash of the UUID, and try every
+ * available channel from there. This means that in most cases a given
+ * uuid will use the same channel. This is a workaround for a Bluez SDP
+ * bug where we are not updating the cache when the channel changes for a
+ * uuid.
+ * TODO: Fix the Bluez SDP caching bug, and go back to random channel
+ * selection
*/
private static class RfcommChannelPicker {
private static final int[] RESERVED_RFCOMM_CHANNELS = new int[] {
@@ -579,7 +588,9 @@
private final LinkedList<Integer> mChannels; // local list of channels left to try
- public RfcommChannelPicker() {
+ private final UUID mUuid;
+
+ public RfcommChannelPicker(UUID uuid) {
synchronized (RfcommChannelPicker.class) {
if (sChannels == null) {
// lazy initialization of non-reserved rfcomm channels
@@ -594,13 +605,21 @@
}
mChannels = (LinkedList<Integer>)sChannels.clone();
}
+ mUuid = uuid;
}
- /* Returns next random channel, or -1 if we're out */
+ /* Returns next channel, or -1 if we're out */
public int nextChannel() {
- if (mChannels.size() == 0) {
- return -1;
+ int channel = mUuid.hashCode(); // always pick the same channel to try first
+ Integer channelInt;
+ while (mChannels.size() > 0) {
+ channelInt = new Integer(channel);
+ if (mChannels.remove(channelInt)) {
+ return channel;
+ }
+ channel = (channel % BluetoothSocket.MAX_RFCOMM_CHANNEL) + 1;
}
- return mChannels.remove(sRandom.nextInt(mChannels.size()));
+
+ return -1;
}
}
@@ -644,6 +663,8 @@
* 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>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
+ * connect to this socket from another device using the same {@link UUID}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
* @param name service name for SDP record
* @param uuid uuid for SDP record
@@ -651,9 +672,9 @@
* @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions, or channel in use.
*/
- public BluetoothServerSocket listenUsingRfcomm(String name, ParcelUuid uuid)
+ public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
throws IOException {
- RfcommChannelPicker picker = new RfcommChannelPicker();
+ RfcommChannelPicker picker = new RfcommChannelPicker(uuid);
BluetoothServerSocket socket;
int channel;
@@ -687,7 +708,8 @@
int handle = -1;
try {
- handle = mService.addRfcommServiceRecord(name, uuid, channel, new Binder());
+ handle = mService.addRfcommServiceRecord(name, new ParcelUuid(uuid), channel,
+ new Binder());
} catch (RemoteException e) {Log.e(TAG, "", e);}
if (handle == -1) {
try {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index d5393ed..ce975c2 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -316,11 +316,28 @@
*/
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
-
- private static IBluetooth sService; /* Guarenteed constant after first object constructed */
+ /**
+ * Lazy initialization. Guaranteed final after first object constructed, or
+ * getService() called.
+ * TODO: Unify implementation of sService amongst BluetoothFoo API's
+ */
+ private static IBluetooth sService;
private final String mAddress;
+ /*package*/ static IBluetooth getService() {
+ synchronized (BluetoothDevice.class) {
+ if (sService == null) {
+ IBinder b = ServiceManager.getService(Context.BLUETOOTH_SERVICE);
+ if (b == null) {
+ throw new RuntimeException("Bluetooth service not available");
+ }
+ sService = IBluetooth.Stub.asInterface(b);
+ }
+ }
+ return sService;
+ }
+
/**
* Create a new BluetoothDevice
* Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
@@ -331,16 +348,7 @@
* @hide
*/
/*package*/ BluetoothDevice(String address) {
- synchronized (BluetoothDevice.class) {
- if (sService == null) {
- IBinder b = ServiceManager.getService(Context.BLUETOOTH_SERVICE);
- if (b == null) {
- throw new RuntimeException("Bluetooth service not available");
- }
- sService = IBluetooth.Stub.asInterface(b);
- }
- }
-
+ getService(); // ensures sService is initialized
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
}
@@ -551,7 +559,7 @@
*/
public boolean fetchUuidsWithSdp() {
try {
- return sService.fetchRemoteUuidsWithSdp(mAddress);
+ return sService.fetchRemoteUuids(mAddress, null, null);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -598,7 +606,7 @@
/**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
- * outgoing connection to this remote device.
+ * outgoing connection to this remote device on given channel.
* <p>The remote device will be authenticated and communication on this
* socket will be encrypted.
* <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
@@ -610,9 +618,34 @@
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
* @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions
+ * @hide
*/
public BluetoothSocket createRfcommSocket(int channel) throws IOException {
- return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel);
+ return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
+ null);
+ }
+
+ /**
+ * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
+ * outgoing connection to this remote device using SDP lookup of uuid.
+ * <p>This is designed to be used with {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer
+ * Bluetooth applications.
+ * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
+ * connection. This will also perform an SDP lookup of the given uuid to
+ * determine which channel to connect to.
+ * <p>The remote device will be authenticated and communication on this
+ * socket will be encrypted.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param uuid service record uuid to lookup RFCOMM channel
+ * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+ * @throws IOException on error, for example Bluetooth not available, or
+ * insufficient permissions
+ */
+ public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+ return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
+ new ParcelUuid(uuid));
}
/**
@@ -628,7 +661,8 @@
* @hide
*/
public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
- return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port);
+ return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port,
+ null);
}
/**
@@ -640,7 +674,7 @@
* @hide
*/
public BluetoothSocket createScoSocket() throws IOException {
- return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1);
+ return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null);
}
/**
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index d126ea4..605bdc1 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -36,13 +36,13 @@
* connection orientated, streaming transport over Bluetooth. It is also known
* as the Serial Port Profile (SPP).
*
- * <p>Use {@link BluetoothDevice#createRfcommSocket} to create a new {@link
- * BluetoothSocket} ready for an outgoing connection to a remote
+ * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to create
+ * a new {@link BluetoothSocket} ready for an outgoing connection to a remote
* {@link BluetoothDevice}.
*
- * <p>Use {@link BluetoothAdapter#listenUsingRfcomm} to create a listening
- * {@link BluetoothServerSocket} ready for incoming connections to the local
- * {@link BluetoothAdapter}.
+ * <p>Use {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord} to
+ * create a listening {@link BluetoothServerSocket} ready for incoming
+ * connections to the local {@link BluetoothAdapter}.
*
* <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread
* safe. In particular, {@link #close} will always immediately abort ongoing
@@ -68,7 +68,7 @@
*/
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
throws IOException {
- mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port);
+ mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
}
/**
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index b9e33f3..7e72590 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -16,11 +16,15 @@
package android.bluetooth;
+import android.bluetooth.IBluetoothCallback;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -38,13 +42,13 @@
* connection orientated, streaming transport over Bluetooth. It is also known
* as the Serial Port Profile (SPP).
*
- * <p>Use {@link BluetoothDevice#createRfcommSocket} to create a new {@link
- * BluetoothSocket} ready for an outgoing connection to a remote
+ * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to create
+ * a new {@link BluetoothSocket} ready for an outgoing connection to a remote
* {@link BluetoothDevice}.
*
- * <p>Use {@link BluetoothAdapter#listenUsingRfcomm} to create a listening
- * {@link BluetoothServerSocket} ready for incoming connections to the local
- * {@link BluetoothAdapter}.
+ * <p>Use {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord} to
+ * create a listening {@link BluetoothServerSocket} ready for incoming
+ * connections to the local {@link BluetoothAdapter}.
*
* <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread
* safe. In particular, {@link #close} will always immediately abort ongoing
@@ -54,6 +58,8 @@
* {@link android.Manifest.permission#BLUETOOTH}
*/
public final class BluetoothSocket implements Closeable {
+ private static final String TAG = "BluetoothSocket";
+
/** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30;
@@ -66,13 +72,15 @@
/*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 */
private final String mAddress; /* remote address */
private final boolean mAuth;
private final boolean mEncrypt;
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
+ private final SdpHelper mSdp;
+
+ private int mPort; /* RFCOMM channel or L2CAP psm */
/** prevents all native calls after destroyNative() */
private boolean mClosed;
@@ -91,16 +99,24 @@
* @param encrypt require the connection to be encrypted
* @param device remote device that this socket can connect to
* @param port remote port
+ * @param uuid SDP uuid
* @throws IOException On error, for example Bluetooth not available, or
* insufficient priveleges
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
- BluetoothDevice device, int port) throws IOException {
- if (type == BluetoothSocket.TYPE_RFCOMM) {
+ BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
+ if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
throw new IOException("Invalid RFCOMM channel: " + port);
}
}
+ if (uuid == null) {
+ mPort = port;
+ mSdp = null;
+ } else {
+ mSdp = new SdpHelper(device, uuid);
+ mPort = -1;
+ }
mType = type;
mAuth = auth;
mEncrypt = encrypt;
@@ -110,7 +126,6 @@
} else {
mAddress = device.getAddress();
}
- mPort = port;
if (fd == -1) {
initSocketNative();
} else {
@@ -123,7 +138,7 @@
}
/**
- * Construct a BluetoothSocket from address.
+ * Construct a BluetoothSocket from address. Used by native code.
* @param type type of socket
* @param fd fd to use for connected socket, or -1 for a new socket
* @param auth require the remote device to be authenticated
@@ -135,7 +150,7 @@
*/
private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
int port) throws IOException {
- this(type, fd, auth, encrypt, new BluetoothDevice(address), port);
+ this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
}
/** @hide */
@@ -160,7 +175,12 @@
mLock.readLock().lock();
try {
if (mClosed) throw new IOException("socket closed");
- connectNative();
+
+ if (mSdp != null) {
+ mPort = mSdp.doSdp(); // blocks
+ }
+
+ connectNative(); // blocks
} finally {
mLock.readLock().unlock();
}
@@ -176,12 +196,15 @@
mLock.readLock().lock();
try {
if (mClosed) return;
+ if (mSdp != null) {
+ mSdp.cancel();
+ }
abortNative();
} finally {
mLock.readLock().unlock();
}
- // all native calls are guarenteed to immediately return after
+ // all native calls are guaranteed to immediately return after
// abortNative(), so this lock should immediatley acquire
mLock.writeLock().lock();
try {
@@ -291,4 +314,62 @@
* use strerr to convert to string error.
*/
/*package*/ native void throwErrnoNative(int errno) throws IOException;
+
+ /**
+ * Helper to perform blocking SDP lookup.
+ */
+ private static class SdpHelper extends IBluetoothCallback.Stub {
+ private final IBluetooth service;
+ private final ParcelUuid uuid;
+ private final BluetoothDevice device;
+ private int channel;
+ private boolean canceled;
+ public SdpHelper(BluetoothDevice device, ParcelUuid uuid) {
+ service = BluetoothDevice.getService();
+ this.device = device;
+ this.uuid = uuid;
+ canceled = false;
+ }
+ /**
+ * Returns the RFCOMM channel for the UUID, or throws IOException
+ * on failure.
+ */
+ public synchronized int doSdp() throws IOException {
+ if (canceled) throw new IOException("Service discovery canceled");
+ channel = -1;
+
+ boolean inProgress = false;
+ try {
+ inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+
+ if (!inProgress) throw new IOException("Unable to start Service Discovery");
+
+ try {
+ /* 12 second timeout as a precaution - onRfcommChannelFound
+ * should always occur before the timeout */
+ wait(12000); // block
+
+ } catch (InterruptedException e) {}
+
+ if (canceled) throw new IOException("Service discovery canceled");
+ if (channel < 1) throw new IOException("Service discovery failed");
+
+ return channel;
+ }
+ /** Object cannot be re-used after calling cancel() */
+ public synchronized void cancel() {
+ if (!canceled) {
+ canceled = true;
+ channel = -1;
+ notifyAll(); // unblock
+ }
+ }
+ public synchronized void onRfcommChannelFound(int channel) {
+ if (!canceled) {
+ this.channel = channel;
+ notifyAll(); // unblock
+ }
+ }
+ }
}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index e54abec..7e752af 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -16,6 +16,7 @@
package android.bluetooth;
+import android.bluetooth.IBluetoothCallback;
import android.os.ParcelUuid;
/**
@@ -53,8 +54,8 @@
String getRemoteName(in String address);
int getRemoteClass(in String address);
ParcelUuid[] getRemoteUuids(in String address);
- boolean fetchRemoteUuidsWithSdp(in String address);
- int getRemoteServiceChannel(in String address,in ParcelUuid uuid);
+ boolean fetchRemoteUuids(in String address, in ParcelUuid uuid, in IBluetoothCallback callback);
+ int getRemoteServiceChannel(in String address, in ParcelUuid uuid);
boolean setPin(in String address, in byte[] pin);
boolean setPasskey(in String address, int passkey);
diff --git a/core/java/android/bluetooth/IBluetoothCallback.aidl b/core/java/android/bluetooth/IBluetoothCallback.aidl
new file mode 100644
index 0000000..8edb3f4
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2009, 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;
+
+/**
+ * System private API for Bluetooth service callbacks.
+ *
+ * {@hide}
+ */
+interface IBluetoothCallback
+{
+ void onRfcommChannelFound(int channel);
+}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 0152223..da1918a 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -55,6 +55,10 @@
private static final int EVENT_RESTART_BLUETOOTH = 2;
private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
+ private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
+ private static final int CREATE_DEVICE_SUCCESS = 0;
+ private static final int CREATE_DEVICE_FAILED = -1;
+
// The time (in millisecs) to delay the pairing attempt after the first
// auto pairing attempt fails. We use an exponential delay with
// INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
@@ -550,14 +554,27 @@
mBluetoothService.updateRemoteDevicePropertiesCache(address);
}
mBluetoothService.sendUuidIntent(address);
+ mBluetoothService.makeServiceChannelCallbacks(address);
}
- private void onCreateDeviceResult(String address, boolean result) {
- if (DBG) {
- log("Result of onCreateDeviceResult:" + result);
- }
- if (!result) {
+ private void onCreateDeviceResult(String address, int result) {
+ if (DBG) log("Result of onCreateDeviceResult:" + result);
+
+ switch (result) {
+ case CREATE_DEVICE_ALREADY_EXISTS:
+ String path = mBluetoothService.getObjectPathFromAddress(address);
+ if (path != null) {
+ mBluetoothService.discoverServicesNative(path, "");
+ break;
+ }
+ Log.w(TAG, "Device exists, but we dont have the bluez path, failing");
+ // fall-through
+ case CREATE_DEVICE_FAILED:
mBluetoothService.sendUuidIntent(address);
+ mBluetoothService.makeServiceChannelCallbacks(address);
+ break;
+ case CREATE_DEVICE_SUCCESS:
+ // nothing to do, UUID intent's will be sent via property changed
}
}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 93133d7..3fdbb68 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -31,6 +31,7 @@
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothCallback;
import android.os.ParcelUuid;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -55,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
public class BluetoothService extends IBluetooth.Stub {
@@ -86,14 +88,39 @@
// This timeout should be greater than the page timeout
private static final int UUID_INTENT_DELAY = 6000;
- private final Map<String, String> mAdapterProperties;
- private final HashMap <String, Map<String, String>> mDeviceProperties;
+ /** Always retrieve RFCOMM channel for these SDP UUIDs */
+ private static final ParcelUuid[] RFCOMM_UUIDS = {
+ BluetoothUuid.Handsfree,
+ BluetoothUuid.HSP,
+ BluetoothUuid.ObexObjectPush };
- private final HashMap <String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
- private final ArrayList <String> mUuidIntentTracker;
+
+ private final Map<String, String> mAdapterProperties;
+ private final HashMap<String, Map<String, String>> mDeviceProperties;
+
+ private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
+ private final ArrayList<String> mUuidIntentTracker;
+ private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
private final HashMap<Integer, Integer> mServiceRecordToPid;
+ private static class RemoteService {
+ public String address;
+ public ParcelUuid uuid;
+ public RemoteService(String address, ParcelUuid uuid) {
+ this.address = address;
+ this.uuid = uuid;
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RemoteService) {
+ RemoteService service = (RemoteService)o;
+ return address.equals(service.address) && uuid.equals(service.uuid);
+ }
+ return false;
+ }
+ }
+
static {
classInitNative();
}
@@ -121,6 +148,7 @@
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
mUuidIntentTracker = new ArrayList<String>();
+ mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, Integer>();
registerForAirplaneMode();
}
@@ -312,8 +340,10 @@
break;
case MESSAGE_UUID_INTENT:
String address = (String)msg.obj;
- if (address != null)
+ if (address != null) {
sendUuidIntent(address);
+ makeServiceChannelCallbacks(address);
+ }
break;
case MESSAGE_DISCOVERABLE_TIMEOUT:
int mode = msg.arg1;
@@ -1064,14 +1094,35 @@
return uuids;
}
- public synchronized boolean fetchRemoteUuidsWithSdp(String address) {
+ /**
+ * Connect and fetch new UUID's using SDP.
+ * The UUID's found are broadcast as intents.
+ * Optionally takes a uuid and callback to fetch the RFCOMM channel for the
+ * a given uuid.
+ * TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success
+ * TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for
+ * callback and broadcast intents.
+ */
+ public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid,
+ IBluetoothCallback callback) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
return false;
}
+ RemoteService service = new RemoteService(address, uuid);
+ if (uuid != null && mUuidCallbackTracker.get(service) != null) {
+ // An SDP query for this address & uuid is already in progress
+ // Do not add this callback for the uuid
+ return false;
+ }
+
if (mUuidIntentTracker.contains(address)) {
// An SDP query for this address is already in progress
+ // Add this uuid onto the in-progress SDP query
+ if (uuid != null) {
+ mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
+ }
return true;
}
@@ -1087,6 +1138,9 @@
}
mUuidIntentTracker.add(address);
+ if (uuid != null) {
+ mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
+ }
Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
message.obj = address;
@@ -1096,6 +1150,7 @@
/**
* Gets the rfcomm channel associated with the UUID.
+ * Pulls records from the cache only.
*
* @param address Address of the remote device
* @param uuid ParcelUuid of the service attribute
@@ -1201,20 +1256,67 @@
// We are storing the rfcomm channel numbers only for the uuids
// we are interested in.
int channel;
- ParcelUuid[] interestedUuids = {BluetoothUuid.Handsfree,
- BluetoothUuid.HSP,
- BluetoothUuid.ObexObjectPush};
+ if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
+
+ ArrayList<ParcelUuid> applicationUuids = new ArrayList();
+
+ synchronized (this) {
+ for (RemoteService service : mUuidCallbackTracker.keySet()) {
+ if (service.address.equals(address)) {
+ applicationUuids.add(service.uuid);
+ }
+ }
+ }
Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
- for (ParcelUuid uuid: interestedUuids) {
+
+ // Retrieve RFCOMM channel for default uuids
+ for (ParcelUuid uuid : RFCOMM_UUIDS) {
if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
- channel =
- getDeviceServiceChannelNative(getObjectPathFromAddress(address), uuid.toString(),
- 0x0004);
+ channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
+ uuid.toString(), 0x0004);
+ if (DBG) log("\tuuid(system): " + uuid + " " + channel);
value.put(uuid, channel);
}
}
- mDeviceServiceChannelCache.put(address, value);
+ // Retrieve RFCOMM channel for application requested uuids
+ for (ParcelUuid uuid : applicationUuids) {
+ if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
+ channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
+ uuid.toString(), 0x0004);
+ if (DBG) log("\tuuid(application): " + uuid + " " + channel);
+ value.put(uuid, channel);
+ }
+ }
+
+ synchronized (this) {
+ // Make application callbacks
+ for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
+ iter.hasNext();) {
+ RemoteService service = iter.next();
+ if (service.address.equals(address)) {
+ channel = -1;
+ if (value.get(service.uuid) != null) {
+ channel = value.get(service.uuid);
+ }
+ if (channel != -1) {
+ if (DBG) log("Making callback for " + service.uuid + " with result " +
+ channel);
+ IBluetoothCallback callback = mUuidCallbackTracker.get(service);
+ if (callback != null) {
+ try {
+ callback.onRfcommChannelFound(channel);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ iter.remove();
+ }
+ }
+ }
+
+ // Update cache
+ mDeviceServiceChannelCache.put(address, value);
+ }
}
/**
@@ -1330,6 +1432,26 @@
if (mUuidIntentTracker.contains(address))
mUuidIntentTracker.remove(address);
+
+ }
+
+ /*package*/ synchronized void makeServiceChannelCallbacks(String address) {
+ for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
+ iter.hasNext();) {
+ RemoteService service = iter.next();
+ if (service.address.equals(address)) {
+ if (DBG) log("Cleaning up failed UUID channel lookup: " + service.address +
+ " " + service.uuid);
+ IBluetoothCallback callback = mUuidCallbackTracker.get(service);
+ if (callback != null) {
+ try {
+ callback.onRfcommChannelFound(-1);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ iter.remove();
+ }
+ }
}
@Override
@@ -1377,6 +1499,11 @@
}
}
}
+ for (RemoteService service : mUuidCallbackTracker.keySet()) {
+ if (service.address.equals(address)) {
+ pw.println("\tPENDING CALLBACK: " + service.uuid);
+ }
+ }
}
String value = getProperty("Devices");
@@ -1508,7 +1635,7 @@
private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
int value);
private native boolean createDeviceNative(String address);
- private native boolean discoverServicesNative(String objectPath, String pattern);
+ /*package*/ native boolean discoverServicesNative(String objectPath, String pattern);
private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
short channel);
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index e37e832..62a50e5 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -36,6 +36,10 @@
namespace android {
+#define CREATE_DEVICE_ALREADY_EXISTS 1
+#define CREATE_DEVICE_SUCCESS 0
+#define CREATE_DEVICE_FAILED -1
+
#ifdef HAVE_BLUETOOTH
static jfieldID field_mNativeData;
@@ -95,7 +99,7 @@
method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult",
"(Ljava/lang/String;I)V");
method_onCreateDeviceResult = env->GetMethodID(clazz, "onCreateDeviceResult",
- "(Ljava/lang/String;Z)V");
+ "(Ljava/lang/String;I)V");
method_onDiscoverServicesResult = env->GetMethodID(clazz, "onDiscoverServicesResult",
"(Ljava/lang/String;Z)V");
@@ -1115,10 +1119,13 @@
LOGV("... Address = %s", address);
- bool result = JNI_TRUE;
+ jint result = CREATE_DEVICE_SUCCESS;
if (dbus_set_error_from_message(&err, msg)) {
+ if (dbus_error_has_name(&err, "org.bluez.Error.AlreadyExists")) {
+ result = CREATE_DEVICE_ALREADY_EXISTS;
+ }
LOG_AND_FREE_DBUS_ERROR(&err);
- result = JNI_FALSE;
+ result = CREATE_DEVICE_FAILED;
}
env->CallVoidMethod(nat->me,
method_onCreateDeviceResult,