summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorNick Pelly <npelly@google.com>2009-10-07 07:44:03 +0200
committerNick Pelly <npelly@google.com>2009-10-07 23:25:24 +0200
commit16fb88a673c41b93c5d57ccb28c2697e7d87701a (patch)
treef6c32d70ca192de4fd6608c931b501263de2766b /core/java
parent64dd5be583bab8218e54068bbf70edc5fc6087c8 (diff)
downloadframeworks_base-16fb88a673c41b93c5d57ccb28c2697e7d87701a.zip
frameworks_base-16fb88a673c41b93c5d57ccb28c2697e7d87701a.tar.gz
frameworks_base-16fb88a673c41b93c5d57ccb28c2697e7d87701a.tar.bz2
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
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java40
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java68
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java12
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java109
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl5
-rw-r--r--core/java/android/bluetooth/IBluetoothCallback.aidl27
-rw-r--r--core/java/android/server/BluetoothEventLoop.java27
-rw-r--r--core/java/android/server/BluetoothService.java155
8 files changed, 376 insertions, 67 deletions
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.HashSet;
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 @@ public final class BluetoothAdapter {
}
/**
- * 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 @@ public final class BluetoothAdapter {
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 @@ public final class BluetoothAdapter {
}
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 @@ public final class BluetoothAdapter {
* 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 @@ public final class BluetoothAdapter {
* @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 @@ public final class BluetoothAdapter {
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,21 +316,16 @@ public final class BluetoothDevice implements Parcelable {
*/
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;
- /**
- * Create a new BluetoothDevice
- * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
- * and is validated in this constructor.
- * @param address valid Bluetooth MAC address
- * @throws RuntimeException Bluetooth is not available on this platform
- * @throws IllegalArgumentException address is invalid
- * @hide
- */
- /*package*/ BluetoothDevice(String address) {
+ /*package*/ static IBluetooth getService() {
synchronized (BluetoothDevice.class) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.BLUETOOTH_SERVICE);
@@ -340,7 +335,20 @@ public final class BluetoothDevice implements Parcelable {
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",
+ * and is validated in this constructor.
+ * @param address valid Bluetooth MAC address
+ * @throws RuntimeException Bluetooth is not available on this platform
+ * @throws IllegalArgumentException address is invalid
+ * @hide
+ */
+ /*package*/ BluetoothDevice(String address) {
+ getService(); // ensures sService is initialized
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
}
@@ -551,7 +559,7 @@ public final class BluetoothDevice implements Parcelable {
*/
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 @@ public final class BluetoothDevice implements Parcelable {
/**
* 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 @@ public final class BluetoothDevice implements Parcelable {
* @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 @@ public final class BluetoothDevice implements Parcelable {
* @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 @@ public final class BluetoothDevice implements Parcelable {
* @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 @@ import java.io.IOException;
* 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 @@ public final class BluetoothServerSocket implements Closeable {
*/
/*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 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* 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 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* {@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 @@ public final class BluetoothSocket implements Closeable {
/*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 @@ public final class BluetoothSocket implements Closeable {
* @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 @@ public final class BluetoothSocket implements Closeable {
} else {
mAddress = device.getAddress();
}
- mPort = port;
if (fd == -1) {
initSocketNative();
} else {
@@ -123,7 +138,7 @@ public final class BluetoothSocket implements Closeable {
}
/**
- * 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 @@ public final class BluetoothSocket implements Closeable {
*/
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 @@ public final class BluetoothSocket implements Closeable {
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 @@ public final class BluetoothSocket implements Closeable {
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 @@ public final class BluetoothSocket implements Closeable {
* 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 @@ interface IBluetooth
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 @@ class BluetoothEventLoop {
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 @@ class BluetoothEventLoop {
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.BluetoothHeadset;
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.io.UnsupportedEncodingException;
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 @@ public class BluetoothService extends IBluetooth.Stub {
// This timeout should be greater than the page timeout
private static final int UUID_INTENT_DELAY = 6000;
+ /** Always retrieve RFCOMM channel for these SDP UUIDs */
+ private static final ParcelUuid[] RFCOMM_UUIDS = {
+ BluetoothUuid.Handsfree,
+ BluetoothUuid.HSP,
+ BluetoothUuid.ObexObjectPush };
+
+
private final Map<String, String> mAdapterProperties;
- private final HashMap <String, Map<String, String>> mDeviceProperties;
+ private final HashMap<String, Map<String, String>> mDeviceProperties;
- private final HashMap <String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
- private final ArrayList <String> mUuidIntentTracker;
+ 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 @@ public class BluetoothService extends IBluetooth.Stub {
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 @@ public class BluetoothService extends IBluetooth.Stub {
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 @@ public class BluetoothService extends IBluetooth.Stub {
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 @@ public class BluetoothService extends IBluetooth.Stub {
}
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 @@ public class BluetoothService extends IBluetooth.Stub {
/**
* 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 @@ public class BluetoothService extends IBluetooth.Stub {
// 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 @@ public class BluetoothService extends IBluetooth.Stub {
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 @@ public class BluetoothService extends IBluetooth.Stub {
}
}
}
+ for (RemoteService service : mUuidCallbackTracker.keySet()) {
+ if (service.address.equals(address)) {
+ pw.println("\tPENDING CALLBACK: " + service.uuid);
+ }
+ }
}
String value = getProperty("Devices");
@@ -1508,7 +1635,7 @@ public class BluetoothService extends IBluetooth.Stub {
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);