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) {