am dc2ccb82: Merge change Ia4879943 into eclair

Merge commit 'dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa' into eclair-mr2

* commit 'dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa':
  Encourage developers to connect RFCOMM by UUID instead of Channel.
This commit is contained in:
Nick Pelly
2009-10-07 15:12:26 -07:00
committed by Android Git Automerger
11 changed files with 392 additions and 75 deletions

View File

@@ -90,6 +90,7 @@ LOCAL_SRC_FILES += \
core/java/android/backup/IRestoreSession.aidl \ core/java/android/backup/IRestoreSession.aidl \
core/java/android/bluetooth/IBluetooth.aidl \ core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \ core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothCallback.aidl \
core/java/android/bluetooth/IBluetoothHeadset.aidl \ core/java/android/bluetooth/IBluetoothHeadset.aidl \
core/java/android/bluetooth/IBluetoothPbap.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \
core/java/android/content/IContentService.aidl \ core/java/android/content/IContentService.aidl \

View File

@@ -25618,7 +25618,7 @@
visibility="public" visibility="public"
> >
</method> </method>
<method name="listenUsingRfcomm" <method name="listenUsingRfcommWithServiceRecord"
return="android.bluetooth.BluetoothServerSocket" return="android.bluetooth.BluetoothServerSocket"
abstract="false" abstract="false"
native="false" native="false"
@@ -25630,7 +25630,7 @@
> >
<parameter name="name" type="java.lang.String"> <parameter name="name" type="java.lang.String">
</parameter> </parameter>
<parameter name="uuid" type="android.os.ParcelUuid"> <parameter name="uuid" type="java.util.UUID">
</parameter> </parameter>
<exception name="IOException" type="java.io.IOException"> <exception name="IOException" type="java.io.IOException">
</exception> </exception>
@@ -26804,7 +26804,7 @@
> >
<implements name="android.os.Parcelable"> <implements name="android.os.Parcelable">
</implements> </implements>
<method name="createRfcommSocket" <method name="createRfcommSocketToServiceRecord"
return="android.bluetooth.BluetoothSocket" return="android.bluetooth.BluetoothSocket"
abstract="false" abstract="false"
native="false" native="false"
@@ -26814,7 +26814,7 @@
deprecated="not deprecated" deprecated="not deprecated"
visibility="public" visibility="public"
> >
<parameter name="channel" type="int"> <parameter name="uuid" type="java.util.UUID">
</parameter> </parameter>
<exception name="IOException" type="java.io.IOException"> <exception name="IOException" type="java.io.IOException">
</exception> </exception>

View File

@@ -31,6 +31,7 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID;
/** /**
* Represents the local Bluetooth adapter. * 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. * 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 class RfcommChannelPicker {
private static final int[] RESERVED_RFCOMM_CHANNELS = new int[] { 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 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) { synchronized (RfcommChannelPicker.class) {
if (sChannels == null) { if (sChannels == null) {
// lazy initialization of non-reserved rfcomm channels // lazy initialization of non-reserved rfcomm channels
@@ -594,13 +605,21 @@ public final class BluetoothAdapter {
} }
mChannels = (LinkedList<Integer>)sChannels.clone(); 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() { public int nextChannel() {
if (mChannels.size() == 0) { int channel = mUuid.hashCode(); // always pick the same channel to try first
return -1; 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 * 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 * to connect to. This SDP record will be removed when this socket is
* closed, or if this application closes unexpectedly. * 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} * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
* @param name service name for SDP record * @param name service name for SDP record
* @param uuid uuid 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 * @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions, or channel in use. * insufficient permissions, or channel in use.
*/ */
public BluetoothServerSocket listenUsingRfcomm(String name, ParcelUuid uuid) public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
throws IOException { throws IOException {
RfcommChannelPicker picker = new RfcommChannelPicker(); RfcommChannelPicker picker = new RfcommChannelPicker(uuid);
BluetoothServerSocket socket; BluetoothServerSocket socket;
int channel; int channel;
@@ -687,7 +708,8 @@ public final class BluetoothAdapter {
int handle = -1; int handle = -1;
try { 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);} } catch (RemoteException e) {Log.e(TAG, "", e);}
if (handle == -1) { if (handle == -1) {
try { try {

View File

@@ -316,11 +316,28 @@ public final class BluetoothDevice implements Parcelable {
*/ */
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; 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; 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 * Create a new BluetoothDevice
* Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB", * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
@@ -331,16 +348,7 @@ public final class BluetoothDevice implements Parcelable {
* @hide * @hide
*/ */
/*package*/ BluetoothDevice(String address) { /*package*/ BluetoothDevice(String address) {
synchronized (BluetoothDevice.class) { getService(); // ensures sService is initialized
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);
}
}
if (!BluetoothAdapter.checkBluetoothAddress(address)) { if (!BluetoothAdapter.checkBluetoothAddress(address)) {
throw new IllegalArgumentException(address + " is not a valid Bluetooth address"); throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
} }
@@ -551,7 +559,7 @@ public final class BluetoothDevice implements Parcelable {
*/ */
public boolean fetchUuidsWithSdp() { public boolean fetchUuidsWithSdp() {
try { try {
return sService.fetchRemoteUuidsWithSdp(mAddress); return sService.fetchRemoteUuids(mAddress, null, null);
} catch (RemoteException e) {Log.e(TAG, "", e);} } catch (RemoteException e) {Log.e(TAG, "", e);}
return false; return false;
} }
@@ -598,7 +606,7 @@ public final class BluetoothDevice implements Parcelable {
/** /**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure * 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 * <p>The remote device will be authenticated and communication on this
* socket will be encrypted. * socket will be encrypted.
* <p>Use {@link BluetoothSocket#connect} to intiate the outgoing * <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 * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
* @throws IOException on error, for example Bluetooth not available, or * @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions * insufficient permissions
* @hide
*/ */
public BluetoothSocket createRfcommSocket(int channel) throws IOException { 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 * @hide
*/ */
public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { 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 * @hide
*/ */
public BluetoothSocket createScoSocket() throws IOException { 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);
} }
/** /**

View File

@@ -36,13 +36,13 @@ import java.io.IOException;
* connection orientated, streaming transport over Bluetooth. It is also known * connection orientated, streaming transport over Bluetooth. It is also known
* as the Serial Port Profile (SPP). * as the Serial Port Profile (SPP).
* *
* <p>Use {@link BluetoothDevice#createRfcommSocket} to create a new {@link * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to create
* BluetoothSocket} ready for an outgoing connection to a remote * a new {@link BluetoothSocket} ready for an outgoing connection to a remote
* {@link BluetoothDevice}. * {@link BluetoothDevice}.
* *
* <p>Use {@link BluetoothAdapter#listenUsingRfcomm} to create a listening * <p>Use {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord} to
* {@link BluetoothServerSocket} ready for incoming connections to the local * create a listening {@link BluetoothServerSocket} ready for incoming
* {@link BluetoothAdapter}. * connections to the local {@link BluetoothAdapter}.
* *
* <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread * <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread
* safe. In particular, {@link #close} will always immediately abort ongoing * 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) /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
throws IOException { throws IOException {
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port); mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
} }
/** /**

View File

@@ -16,11 +16,15 @@
package android.bluetooth; 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.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.locks.ReentrantReadWriteLock; 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 * connection orientated, streaming transport over Bluetooth. It is also known
* as the Serial Port Profile (SPP). * as the Serial Port Profile (SPP).
* *
* <p>Use {@link BluetoothDevice#createRfcommSocket} to create a new {@link * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to create
* BluetoothSocket} ready for an outgoing connection to a remote * a new {@link BluetoothSocket} ready for an outgoing connection to a remote
* {@link BluetoothDevice}. * {@link BluetoothDevice}.
* *
* <p>Use {@link BluetoothAdapter#listenUsingRfcomm} to create a listening * <p>Use {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord} to
* {@link BluetoothServerSocket} ready for incoming connections to the local * create a listening {@link BluetoothServerSocket} ready for incoming
* {@link BluetoothAdapter}. * connections to the local {@link BluetoothAdapter}.
* *
* <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread * <p>{@link BluetoothSocket} and {@link BluetoothServerSocket} are thread
* safe. In particular, {@link #close} will always immediately abort ongoing * 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} * {@link android.Manifest.permission#BLUETOOTH}
*/ */
public final class BluetoothSocket implements Closeable { public final class BluetoothSocket implements Closeable {
private static final String TAG = "BluetoothSocket";
/** @hide */ /** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30; public static final int MAX_RFCOMM_CHANNEL = 30;
@@ -66,13 +72,15 @@ public final class BluetoothSocket implements Closeable {
/*package*/ static final int EADDRINUSE = 98; /*package*/ static final int EADDRINUSE = 98;
private final int mType; /* one of TYPE_RFCOMM etc */ 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 BluetoothDevice mDevice; /* remote device */
private final String mAddress; /* remote address */ private final String mAddress; /* remote address */
private final boolean mAuth; private final boolean mAuth;
private final boolean mEncrypt; private final boolean mEncrypt;
private final BluetoothInputStream mInputStream; private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream; private final BluetoothOutputStream mOutputStream;
private final SdpHelper mSdp;
private int mPort; /* RFCOMM channel or L2CAP psm */
/** prevents all native calls after destroyNative() */ /** prevents all native calls after destroyNative() */
private boolean mClosed; private boolean mClosed;
@@ -91,16 +99,24 @@ public final class BluetoothSocket implements Closeable {
* @param encrypt require the connection to be encrypted * @param encrypt require the connection to be encrypted
* @param device remote device that this socket can connect to * @param device remote device that this socket can connect to
* @param port remote port * @param port remote port
* @param uuid SDP uuid
* @throws IOException On error, for example Bluetooth not available, or * @throws IOException On error, for example Bluetooth not available, or
* insufficient priveleges * insufficient priveleges
*/ */
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port) throws IOException { BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
if (type == BluetoothSocket.TYPE_RFCOMM) { if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
if (port < 1 || port > MAX_RFCOMM_CHANNEL) { if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
throw new IOException("Invalid RFCOMM channel: " + port); throw new IOException("Invalid RFCOMM channel: " + port);
} }
} }
if (uuid == null) {
mPort = port;
mSdp = null;
} else {
mSdp = new SdpHelper(device, uuid);
mPort = -1;
}
mType = type; mType = type;
mAuth = auth; mAuth = auth;
mEncrypt = encrypt; mEncrypt = encrypt;
@@ -110,7 +126,6 @@ public final class BluetoothSocket implements Closeable {
} else { } else {
mAddress = device.getAddress(); mAddress = device.getAddress();
} }
mPort = port;
if (fd == -1) { if (fd == -1) {
initSocketNative(); initSocketNative();
} else { } 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 type type of socket
* @param fd fd to use for connected socket, or -1 for a new socket * @param fd fd to use for connected socket, or -1 for a new socket
* @param auth require the remote device to be authenticated * @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, private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
int port) throws IOException { int port) throws IOException {
this(type, fd, auth, encrypt, new BluetoothDevice(address), port); this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null);
} }
/** @hide */ /** @hide */
@@ -160,7 +175,12 @@ public final class BluetoothSocket implements Closeable {
mLock.readLock().lock(); mLock.readLock().lock();
try { try {
if (mClosed) throw new IOException("socket closed"); if (mClosed) throw new IOException("socket closed");
connectNative();
if (mSdp != null) {
mPort = mSdp.doSdp(); // blocks
}
connectNative(); // blocks
} finally { } finally {
mLock.readLock().unlock(); mLock.readLock().unlock();
} }
@@ -176,12 +196,15 @@ public final class BluetoothSocket implements Closeable {
mLock.readLock().lock(); mLock.readLock().lock();
try { try {
if (mClosed) return; if (mClosed) return;
if (mSdp != null) {
mSdp.cancel();
}
abortNative(); abortNative();
} finally { } finally {
mLock.readLock().unlock(); 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 // abortNative(), so this lock should immediatley acquire
mLock.writeLock().lock(); mLock.writeLock().lock();
try { try {
@@ -291,4 +314,62 @@ public final class BluetoothSocket implements Closeable {
* use strerr to convert to string error. * use strerr to convert to string error.
*/ */
/*package*/ native void throwErrnoNative(int errno) throws IOException; /*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
}
}
}
} }

View File

@@ -16,6 +16,7 @@
package android.bluetooth; package android.bluetooth;
import android.bluetooth.IBluetoothCallback;
import android.os.ParcelUuid; import android.os.ParcelUuid;
/** /**
@@ -53,8 +54,8 @@ interface IBluetooth
String getRemoteName(in String address); String getRemoteName(in String address);
int getRemoteClass(in String address); int getRemoteClass(in String address);
ParcelUuid[] getRemoteUuids(in String address); ParcelUuid[] getRemoteUuids(in String address);
boolean fetchRemoteUuidsWithSdp(in String address); boolean fetchRemoteUuids(in String address, in ParcelUuid uuid, in IBluetoothCallback callback);
int getRemoteServiceChannel(in String address,in ParcelUuid uuid); int getRemoteServiceChannel(in String address, in ParcelUuid uuid);
boolean setPin(in String address, in byte[] pin); boolean setPin(in String address, in byte[] pin);
boolean setPasskey(in String address, int passkey); boolean setPasskey(in String address, int passkey);

View File

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

View File

@@ -55,6 +55,10 @@ class BluetoothEventLoop {
private static final int EVENT_RESTART_BLUETOOTH = 2; private static final int EVENT_RESTART_BLUETOOTH = 2;
private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3; 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 // The time (in millisecs) to delay the pairing attempt after the first
// auto pairing attempt fails. We use an exponential delay with // auto pairing attempt fails. We use an exponential delay with
// INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
@@ -550,14 +554,27 @@ class BluetoothEventLoop {
mBluetoothService.updateRemoteDevicePropertiesCache(address); mBluetoothService.updateRemoteDevicePropertiesCache(address);
} }
mBluetoothService.sendUuidIntent(address); mBluetoothService.sendUuidIntent(address);
mBluetoothService.makeServiceChannelCallbacks(address);
} }
private void onCreateDeviceResult(String address, boolean result) { private void onCreateDeviceResult(String address, int result) {
if (DBG) { if (DBG) log("Result of onCreateDeviceResult:" + result);
log("Result of onCreateDeviceResult:" + result);
} switch (result) {
if (!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.sendUuidIntent(address);
mBluetoothService.makeServiceChannelCallbacks(address);
break;
case CREATE_DEVICE_SUCCESS:
// nothing to do, UUID intent's will be sent via property changed
} }
} }

View File

@@ -31,6 +31,7 @@ import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid; import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth; import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothCallback;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -55,6 +56,7 @@ import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
public class BluetoothService extends IBluetooth.Stub { 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 // This timeout should be greater than the page timeout
private static final int UUID_INTENT_DELAY = 6000; private static final int UUID_INTENT_DELAY = 6000;
private final Map<String, String> mAdapterProperties; /** Always retrieve RFCOMM channel for these SDP UUIDs */
private final HashMap <String, Map<String, String>> mDeviceProperties; 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 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 { static {
classInitNative(); classInitNative();
} }
@@ -121,6 +148,7 @@ public class BluetoothService extends IBluetooth.Stub {
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>(); mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
mUuidIntentTracker = new ArrayList<String>(); mUuidIntentTracker = new ArrayList<String>();
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, Integer>(); mServiceRecordToPid = new HashMap<Integer, Integer>();
registerForAirplaneMode(); registerForAirplaneMode();
} }
@@ -312,8 +340,10 @@ public class BluetoothService extends IBluetooth.Stub {
break; break;
case MESSAGE_UUID_INTENT: case MESSAGE_UUID_INTENT:
String address = (String)msg.obj; String address = (String)msg.obj;
if (address != null) if (address != null) {
sendUuidIntent(address); sendUuidIntent(address);
makeServiceChannelCallbacks(address);
}
break; break;
case MESSAGE_DISCOVERABLE_TIMEOUT: case MESSAGE_DISCOVERABLE_TIMEOUT:
int mode = msg.arg1; int mode = msg.arg1;
@@ -1064,14 +1094,35 @@ public class BluetoothService extends IBluetooth.Stub {
return uuids; 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"); mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothAdapter.checkBluetoothAddress(address)) { if (!BluetoothAdapter.checkBluetoothAddress(address)) {
return false; 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)) { if (mUuidIntentTracker.contains(address)) {
// An SDP query for this address is already in progress // 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; return true;
} }
@@ -1087,6 +1138,9 @@ public class BluetoothService extends IBluetooth.Stub {
} }
mUuidIntentTracker.add(address); mUuidIntentTracker.add(address);
if (uuid != null) {
mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
}
Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
message.obj = address; message.obj = address;
@@ -1096,6 +1150,7 @@ public class BluetoothService extends IBluetooth.Stub {
/** /**
* Gets the rfcomm channel associated with the UUID. * Gets the rfcomm channel associated with the UUID.
* Pulls records from the cache only.
* *
* @param address Address of the remote device * @param address Address of the remote device
* @param uuid ParcelUuid of the service attribute * @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 storing the rfcomm channel numbers only for the uuids
// we are interested in. // we are interested in.
int channel; int channel;
ParcelUuid[] interestedUuids = {BluetoothUuid.Handsfree, if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
BluetoothUuid.HSP,
BluetoothUuid.ObexObjectPush}; 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>(); 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)) { if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
channel = channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
getDeviceServiceChannelNative(getObjectPathFromAddress(address), uuid.toString(), uuid.toString(), 0x0004);
0x0004); if (DBG) log("\tuuid(system): " + uuid + " " + channel);
value.put(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)) if (mUuidIntentTracker.contains(address))
mUuidIntentTracker.remove(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 @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"); String value = getProperty("Devices");
@@ -1508,7 +1635,7 @@ public class BluetoothService extends IBluetooth.Stub {
private native boolean setDevicePropertyBooleanNative(String objectPath, String key, private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
int value); int value);
private native boolean createDeviceNative(String address); 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, private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
short channel); short channel);

View File

@@ -36,6 +36,10 @@
namespace android { namespace android {
#define CREATE_DEVICE_ALREADY_EXISTS 1
#define CREATE_DEVICE_SUCCESS 0
#define CREATE_DEVICE_FAILED -1
#ifdef HAVE_BLUETOOTH #ifdef HAVE_BLUETOOTH
static jfieldID field_mNativeData; static jfieldID field_mNativeData;
@@ -95,7 +99,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult", method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult",
"(Ljava/lang/String;I)V"); "(Ljava/lang/String;I)V");
method_onCreateDeviceResult = env->GetMethodID(clazz, "onCreateDeviceResult", method_onCreateDeviceResult = env->GetMethodID(clazz, "onCreateDeviceResult",
"(Ljava/lang/String;Z)V"); "(Ljava/lang/String;I)V");
method_onDiscoverServicesResult = env->GetMethodID(clazz, "onDiscoverServicesResult", method_onDiscoverServicesResult = env->GetMethodID(clazz, "onDiscoverServicesResult",
"(Ljava/lang/String;Z)V"); "(Ljava/lang/String;Z)V");
@@ -1115,10 +1119,13 @@ void onCreateDeviceResult(DBusMessage *msg, void *user, void *n) {
LOGV("... Address = %s", address); LOGV("... Address = %s", address);
bool result = JNI_TRUE; jint result = CREATE_DEVICE_SUCCESS;
if (dbus_set_error_from_message(&err, msg)) { 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); LOG_AND_FREE_DBUS_ERROR(&err);
result = JNI_FALSE; result = CREATE_DEVICE_FAILED;
} }
env->CallVoidMethod(nat->me, env->CallVoidMethod(nat->me,
method_onCreateDeviceResult, method_onCreateDeviceResult,