From 1caa6d111eff6814760ec156b14adc29aa3aae6c Mon Sep 17 00:00:00 2001 From: Jaikumar Ganesh Date: Fri, 18 Sep 2009 11:32:54 -0700 Subject: [PATCH] Add new API for fetching UUIDs using SDP. Add new API which clients can use to force an SDP query. The result is broadcast using an intent having the UUIDs. The intent is broadcast after a timeout, in case of an error. This timeout is greater than the page timeout. Change-Id: I61e6db4c05b34c42f679a66987e37e2063a793b6 --- .../android/bluetooth/BluetoothDevice.java | 45 ++++++++++++ core/java/android/bluetooth/IBluetooth.aidl | 1 + .../android/server/BluetoothEventLoop.java | 23 ++++++ .../java/android/server/BluetoothService.java | 61 ++++++++++++++++ .../jni/android_server_BluetoothEventLoop.cpp | 54 ++++++++++++++ core/jni/android_server_BluetoothService.cpp | 73 +++++++++++++++++++ 6 files changed, 257 insertions(+) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index f81ba731ef0af..b52a82260aae2 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -28,6 +28,7 @@ import android.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.UUID; /** * Represents a remote Bluetooth device. @@ -225,6 +226,20 @@ public final class BluetoothDevice implements Parcelable { /** @hide */ public static final String EXTRA_PASSKEY = "android.bluetooth.device.extra.PASSKEY"; + /** + * Broadcast Action: This intent is used to broadcast the {@link UUID} + * wrapped as a {@link ParcelUuid} of the remote device after it has been + * fetched. This intent is sent only when the UUIDs of the remote device + * are requested to be fetched using Service Discovery Protocol + *

Always contains the extra field {@link #EXTRA_DEVICE} + *

Always contains the extra filed {@link #EXTRA_UUID} + *

Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_UUID = + "android.bleutooth.device.action.UUID"; + /** * Broadcast Action: Indicates a failure to retrieve the name of a remote * device. @@ -292,6 +307,15 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; + /** + * Used as an extra field in {@link #ACTION_UUID} intents, + * Contains the {@link ParcelUuid}s of the remote device which is a parcelable + * version of {@link UUID}. + * @hide + */ + public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + + private static IBluetooth sService; /* Guarenteed constant after first object constructed */ private final String mAddress; @@ -507,6 +531,27 @@ public final class BluetoothDevice implements Parcelable { return null; } + /** + * Perform a SDP query on the remote device to get the UUIDs + * supported. This API is asynchronous and an Intent is sent, + * with the UUIDs supported by the remote end. If there is an error + * in getting the SDP records or if the process takes a long time, + * an Intent is sent with the UUIDs that is currently present in the + * cache. Clients should use the {@link getUuids} to get UUIDs + * is SDP is not to be performed. + * + * @return False if the sanity check fails, True if the process + * of initiating an ACL connection to the remote device + * was started. + * @hide + */ + public boolean fetchUuidsWithSdp() { + try { + return sService.fetchRemoteUuidsWithSdp(mAddress); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + /** @hide */ public int getServiceChannel(ParcelUuid uuid) { try { diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 04c8ec9545b1c..203a61d8a1f68 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -53,6 +53,7 @@ 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 setPin(in String address, in byte[] pin); diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index ba5330723e833..ba0a0d44a519e 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -330,6 +330,9 @@ class BluetoothEventLoop { Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); return; } + if (DBG) { + log("Device property changed:" + address + "property:" + name); + } BluetoothDevice device = mAdapter.getRemoteDevice(address); if (name.equals("Name")) { Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); @@ -366,6 +369,7 @@ class BluetoothEventLoop { uuid = str.toString(); } mBluetoothService.setRemoteDeviceProperty(address, name, uuid); + mBluetoothService.sendUuidIntent(address); } else if (name.equals("Paired")) { if (propValues[1].equals("true")) { mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); @@ -528,6 +532,25 @@ class BluetoothEventLoop { return; } + private void onDiscoverServicesResult(String deviceObjectPath, boolean result) { + String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); + // We don't parse the xml here, instead just query Bluez for the properties. + if (result) { + String[] properties = mBluetoothService.getRemoteDeviceProperties(address); + mBluetoothService.addRemoteDeviceProperties(address, properties); + } + mBluetoothService.sendUuidIntent(address); + } + + private void onCreateDeviceResult(String address, boolean result) { + if (DBG) { + log("Result of onCreateDeviceResult:" + result); + } + if (!result) { + mBluetoothService.sendUuidIntent(address); + } + } + private void onRestartRequired() { if (mBluetoothService.isEnabled()) { Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " + diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index c0e4f341964e0..ce62f070073c4 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -76,10 +76,17 @@ public class BluetoothService extends IBluetooth.Stub { private static final int MESSAGE_REGISTER_SDP_RECORDS = 1; private static final int MESSAGE_FINISH_DISABLE = 2; + private static final int MESSAGE_UUID_INTENT = 3; + + // The timeout used to sent the UUIDs Intent + // This timeout should be greater than the page timeout + private static final int UUID_INTENT_DELAY = 6000; private final Map mAdapterProperties; private final HashMap > mDeviceProperties; + private final ArrayList mUuidIntentTracker; + static { classInitNative(); } @@ -104,6 +111,7 @@ public class BluetoothService extends IBluetooth.Stub { mIsDiscovering = false; mAdapterProperties = new HashMap(); mDeviceProperties = new HashMap>(); + mUuidIntentTracker = new ArrayList(); registerForAirplaneMode(); } @@ -291,6 +299,11 @@ public class BluetoothService extends IBluetooth.Stub { case MESSAGE_FINISH_DISABLE: finishDisable(msg.arg1 != 0); break; + case MESSAGE_UUID_INTENT: + String address = (String)msg.obj; + if (address != null) + sendUuidIntent(address); + break; } } }; @@ -976,6 +989,10 @@ public class BluetoothService extends IBluetooth.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { return null; } + return getUuidFromCache(address); + } + + private ParcelUuid[] getUuidFromCache(String address) { String value = getRemoteDeviceProperty(address, "UUIDs"); if (value == null) return null; @@ -990,6 +1007,36 @@ public class BluetoothService extends IBluetooth.Stub { return uuids; } + public synchronized boolean fetchRemoteUuidsWithSdp(String address) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + return false; + } + + if (mUuidIntentTracker.contains(address)) { + // An SDP query for this address is already in progress + return true; + } + + boolean ret; + if (getBondState(address) == BluetoothDevice.BOND_BONDED) { + String path = getObjectPathFromAddress(address); + if (path == null) return false; + + // Use an empty string for the UUID pattern + ret = discoverServicesNative(path, ""); + } else { + ret = createDeviceNative(address); + } + + mUuidIntentTracker.add(address); + + Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); + message.obj = address; + mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY); + return ret; + } + /** * Gets the rfcomm channel associated with the UUID. * @@ -1121,6 +1168,18 @@ public class BluetoothService extends IBluetooth.Stub { Settings.System.AIRPLANE_MODE_ON, 0) == 1; } + /* Broadcast the Uuid intent */ + /*package*/ synchronized void sendUuidIntent(String address) { + if (mUuidIntentTracker.contains(address)) { + ParcelUuid[] uuid = getUuidFromCache(address); + Intent intent = new Intent(BluetoothDevice.ACTION_UUID); + intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + + mUuidIntentTracker.remove(address); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive + "\n"); @@ -1284,4 +1343,6 @@ public class BluetoothService extends IBluetooth.Stub { private native boolean setPairingConfirmationNative(String address, boolean confirm, int nativeData); private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value); + private native boolean createDeviceNative(String address); + private native boolean discoverServicesNative(String objectPath, String pattern); } diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index e703ed85032d7..e37e8323a2798 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -48,6 +48,8 @@ static jmethodID method_onDeviceRemoved; static jmethodID method_onDeviceDisconnectRequested; static jmethodID method_onCreatePairedDeviceResult; +static jmethodID method_onCreateDeviceResult; +static jmethodID method_onDiscoverServicesResult; static jmethodID method_onGetDeviceServiceChannelResult; static jmethodID method_onRequestPinCode; @@ -92,6 +94,10 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onCreatePairedDeviceResult = env->GetMethodID(clazz, "onCreatePairedDeviceResult", "(Ljava/lang/String;I)V"); + method_onCreateDeviceResult = env->GetMethodID(clazz, "onCreateDeviceResult", + "(Ljava/lang/String;Z)V"); + method_onDiscoverServicesResult = env->GetMethodID(clazz, "onDiscoverServicesResult", + "(Ljava/lang/String;Z)V"); method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize", "(Ljava/lang/String;Ljava/lang/String;)Z"); @@ -1097,6 +1103,54 @@ done: free(user); } +void onCreateDeviceResult(DBusMessage *msg, void *user, void *n) { + LOGV(__FUNCTION__); + + native_data_t *nat = (native_data_t *)n; + const char *address= (const char *)user; + DBusError err; + dbus_error_init(&err); + JNIEnv *env; + nat->vm->GetEnv((void**)&env, nat->envVer); + + LOGV("... Address = %s", address); + + bool result = JNI_TRUE; + if (dbus_set_error_from_message(&err, msg)) { + LOG_AND_FREE_DBUS_ERROR(&err); + result = JNI_FALSE; + } + env->CallVoidMethod(nat->me, + method_onCreateDeviceResult, + env->NewStringUTF(address), + result); + free(user); +} + +void onDiscoverServicesResult(DBusMessage *msg, void *user, void *n) { + LOGV(__FUNCTION__); + + native_data_t *nat = (native_data_t *)n; + const char *path = (const char *)user; + DBusError err; + dbus_error_init(&err); + JNIEnv *env; + nat->vm->GetEnv((void**)&env, nat->envVer); + + LOGV("... Device Path = %s", path); + + bool result = JNI_TRUE; + if (dbus_set_error_from_message(&err, msg)) { + LOG_AND_FREE_DBUS_ERROR(&err); + result = JNI_FALSE; + } + env->CallVoidMethod(nat->me, + method_onDiscoverServicesResult, + env->NewStringUTF(path), + result); + free(user); +} + void onGetDeviceServiceChannelResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp index 0b71acb5a85a4..c432ed9ab2abb 100644 --- a/core/jni/android_server_BluetoothService.cpp +++ b/core/jni/android_server_BluetoothService.cpp @@ -66,6 +66,8 @@ extern DBusHandlerResult agent_event_filter(DBusConnection *conn, DBusMessage *msg, void *data); void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *nat); +void onDiscoverServicesResult(DBusMessage *msg, void *user, void *nat); +void onCreateDeviceResult(DBusMessage *msg, void *user, void *nat); /** Get native data stored in the opaque (Java code maintained) pointer mNativeData @@ -757,6 +759,75 @@ static jboolean setDevicePropertyBooleanNative(JNIEnv *env, jobject object, #endif } + +static jboolean createDeviceNative(JNIEnv *env, jobject object, + jstring address) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + jobject eventLoop = env->GetObjectField(object, field_mEventLoop); + struct event_loop_native_data_t *eventLoopNat = + get_EventLoop_native_data(env, eventLoop); + + if (nat && eventLoopNat) { + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV("... address = %s", c_address); + char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char)); + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + + bool ret = dbus_func_args_async(env, nat->conn, -1, + onCreateDeviceResult, + context_address, + eventLoopNat, + get_adapter_path(env, object), + DBUS_ADAPTER_IFACE, + "CreateDevice", + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(address, c_address); + return ret ? JNI_TRUE : JNI_FALSE; + } +#endif + return JNI_FALSE; +} + +static jboolean discoverServicesNative(JNIEnv *env, jobject object, + jstring path, jstring pattern) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + jobject eventLoop = env->GetObjectField(object, field_mEventLoop); + struct event_loop_native_data_t *eventLoopNat = + get_EventLoop_native_data(env, eventLoop); + + if (nat && eventLoopNat) { + const char *c_path = env->GetStringUTFChars(path, NULL); + const char *c_pattern = env->GetStringUTFChars(pattern, NULL); + int len = env->GetStringLength(path) + 1; + char *context_path = (char *)calloc(len, sizeof(char)); + strlcpy(context_path, c_path, len); // for callback + + LOGV("... Object Path = %s", c_path); + LOGV("... Pattern = %s, strlen = %d", c_pattern, strlen(c_pattern)); + + bool ret = dbus_func_args_async(env, nat->conn, -1, + onDiscoverServicesResult, + context_path, + eventLoopNat, + c_path, + DBUS_DEVICE_IFACE, + "DiscoverServices", + DBUS_TYPE_STRING, &c_pattern, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(path, c_path); + env->ReleaseStringUTFChars(pattern, c_pattern); + return ret ? JNI_TRUE : JNI_FALSE; + } +#endif + return JNI_FALSE; +} + + static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"classInitNative", "()V", (void*)classInitNative}, @@ -797,6 +868,8 @@ static JNINativeMethod sMethods[] = { (void *)cancelPairingUserInputNative}, {"setDevicePropertyBooleanNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setDevicePropertyBooleanNative}, + {"createDeviceNative", "(Ljava/lang/String;)Z", (void *)createDeviceNative}, + {"discoverServicesNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)discoverServicesNative}, }; int register_android_server_BluetoothService(JNIEnv *env) {