diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-10-07 18:04:45 -0400 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-10-07 18:04:45 -0400 |
commit | dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa (patch) | |
tree | d1d125e26a0606145538aec363dbfe94758921dd /core/java/android | |
parent | 35b38cefcc92f1ed599a652ac5736ab9e9e75039 (diff) | |
parent | 16fb88a673c41b93c5d57ccb28c2697e7d87701a (diff) | |
download | frameworks_base-dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa.zip frameworks_base-dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa.tar.gz frameworks_base-dc2ccb82f96b6a7be7a7b262b81d3aa1422f46fa.tar.bz2 |
Merge change Ia4879943 into eclair
* changes:
Encourage developers to connect RFCOMM by UUID instead of Channel.
Diffstat (limited to 'core/java/android')
-rw-r--r-- | core/java/android/bluetooth/BluetoothAdapter.java | 40 | ||||
-rw-r--r-- | core/java/android/bluetooth/BluetoothDevice.java | 68 | ||||
-rw-r--r-- | core/java/android/bluetooth/BluetoothServerSocket.java | 12 | ||||
-rw-r--r-- | core/java/android/bluetooth/BluetoothSocket.java | 109 | ||||
-rw-r--r-- | core/java/android/bluetooth/IBluetooth.aidl | 5 | ||||
-rw-r--r-- | core/java/android/bluetooth/IBluetoothCallback.aidl | 27 | ||||
-rw-r--r-- | core/java/android/server/BluetoothEventLoop.java | 27 | ||||
-rw-r--r-- | core/java/android/server/BluetoothService.java | 155 |
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); |