From 32d8571f509c392dca732c243e9b2138c15daecf Mon Sep 17 00:00:00 2001 From: Jaikumar Ganesh Date: Thu, 10 Sep 2009 22:00:05 -0700 Subject: [PATCH] Changes for BT 2.1 1) Handle incoming 2.1 pairing requests 2) Modify displaying error messages on bond failures. 3) Add delay while accepting incoming pairing for certain 2.1 devices. When MITM is on, the link key request might come more than once. Auto accept with a delay. 4) Handle DisplayPasskey callback for pairing a 2.1 keyboard with a 2.1 device --- .../android/bluetooth/BluetoothDevice.java | 18 +++++- .../android/server/BluetoothEventLoop.java | 54 ++++++++++++++++-- .../java/android/server/BluetoothService.java | 1 - .../jni/android_server_BluetoothEventLoop.cpp | 57 +++++++++++++++++-- core/jni/android_server_BluetoothService.cpp | 4 +- 5 files changed, 118 insertions(+), 16 deletions(-) diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index a9cec5033b661..b1861acb708b9 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -250,7 +250,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public static final int BOND_SUCCESS = 0; /** A bond attempt failed because pins did not match, or remote device did - * not respond to pin request in time + * not respond to pin request in time * @hide */ public static final int UNBOND_REASON_AUTH_FAILED = 1; /** A bond attempt failed because the other side explicilty rejected @@ -266,9 +266,15 @@ public final class BluetoothDevice implements Parcelable { /** A bond attempt failed because a discovery is in progress * @hide */ public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; + /** A bond attempt failed because of authentication timeout + * @hide */ + public static final int UNBOND_REASON_AUTH_TIMEOUT = 6; + /** A bond attempt failed because of repeated attempts + * @hide */ + public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7; /** An existing bond was explicitly revoked * @hide */ - public static final int UNBOND_REASON_REMOVED = 6; + public static final int UNBOND_REASON_REMOVED = 8; /** The user will be prompted to enter a pin * @hide */ @@ -278,7 +284,13 @@ public final class BluetoothDevice implements Parcelable { public static final int PAIRING_VARIANT_PASSKEY = 1; /** The user will be prompted to confirm the passkey displayed on the screen * @hide */ - public static final int PAIRING_VARIANT_CONFIRMATION = 2; + public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; + /** The user will be prompted to accept or deny the incoming pairing request + * @hide */ + public static final int PAIRING_VARIANT_CONSENT = 3; + /** The user will be prompted to enter the passkey displayed on remote device + * @hide */ + public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; private static IBluetooth sService; /* Guarenteed constant after first object constructed */ diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index d0d4b84997fe3..1ed5c49e592e8 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -17,8 +17,8 @@ package android.server; import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.content.Context; @@ -53,6 +53,7 @@ class BluetoothEventLoop { private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; private static final int EVENT_RESTART_BLUETOOTH = 2; + private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3; // The time (in millisecs) to delay the pairing attempt after the first // auto pairing attempt fails. We use an exponential delay with @@ -67,9 +68,10 @@ class BluetoothEventLoop { private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { + String address = null; switch (msg.what) { case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: - String address = (String)msg.obj; + address = (String)msg.obj; if (address != null) { mBluetoothService.createBond(address); return; @@ -78,6 +80,12 @@ class BluetoothEventLoop { case EVENT_RESTART_BLUETOOTH: mBluetoothService.restart(); break; + case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT: + address = (String)msg.obj; + if (address != null) { + mBluetoothService.setPairingConfirmation(address, true); + } + break; } } }; @@ -239,6 +247,7 @@ class BluetoothEventLoop { addDevice(address, properties); } } + mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING); return; } @@ -390,7 +399,32 @@ class BluetoothEventLoop { return address; } - private void onRequestConfirmation(String objectPath, int passkey, int nativeData) { + private void onRequestPairingConsent(String objectPath, int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + /* The link key will not be stored if the incoming request has MITM + * protection switched on. Unfortunately, some devices have MITM + * switched on even though their capabilities are NoInputNoOutput, + * so we may get this request many times. Also if we respond immediately, + * the other end is unable to handle it. Delay sending the message. + */ + if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) { + Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); + message.obj = address; + mHandler.sendMessageDelayed(message, 1500); + return; + } + + Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); + intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_CONSENT); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + return; + } + + private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) { String address = checkPairingRequestAndGetAddress(objectPath, nativeData); if (address == null) return; @@ -398,7 +432,7 @@ class BluetoothEventLoop { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_CONFIRMATION); + BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION); mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); return; } @@ -447,6 +481,18 @@ class BluetoothEventLoop { return; } + private void onDisplayPasskey(String objectPath, int passkey, int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); + intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); + intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + } + private boolean onAgentAuthorize(String objectPath, String deviceUuid) { String address = mBluetoothService.getAddressFromObjectPath(objectPath); if (address == null) { diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 53fb03cfdae0a..6ce0f5f070ba4 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -1266,5 +1266,4 @@ 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); - } diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index 79a3247ef131f..e703ed85032d7 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -52,7 +52,9 @@ static jmethodID method_onGetDeviceServiceChannelResult; static jmethodID method_onRequestPinCode; static jmethodID method_onRequestPasskey; -static jmethodID method_onRequestConfirmation; +static jmethodID method_onRequestPasskeyConfirmation; +static jmethodID method_onRequestPairingConsent; +static jmethodID method_onDisplayPasskey; static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; @@ -98,7 +100,11 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;I)V"); method_onRequestPasskey = env->GetMethodID(clazz, "onRequestPasskey", "(Ljava/lang/String;I)V"); - method_onRequestConfirmation = env->GetMethodID(clazz, "onRequestConfirmation", + method_onRequestPasskeyConfirmation = env->GetMethodID(clazz, "onRequestPasskeyConfirmation", + "(Ljava/lang/String;II)V"); + method_onRequestPairingConsent = env->GetMethodID(clazz, "onRequestPairingConsent", + "(Ljava/lang/String;I)V"); + method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey", "(Ljava/lang/String;II)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); @@ -230,7 +236,7 @@ static jboolean setUpEventLoop(native_data_t *nat) { const char * get_adapter_path(DBusConnection *conn) { - DBusMessage *msg, *reply = NULL; + DBusMessage *msg = NULL, *reply = NULL; DBusError err; const char *device_path = NULL; int attempt = 0; @@ -856,9 +862,7 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Cancel")) { - env->CallVoidMethod(nat->me, method_onAgentCancel); - // reply DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { @@ -938,6 +942,24 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, env->NewStringUTF(object_path), int(msg)); goto success; + } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "DisplayPasskey")) { + char *object_path; + uint32_t passkey; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestPasskey() method", __FUNCTION__); + goto failure; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onDisplayPasskey, + env->NewStringUTF(object_path), + passkey, + int(msg)); + goto success; } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "RequestConfirmation")) { char *object_path; @@ -951,11 +973,26 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, } dbus_message_ref(msg); // increment refcount because we pass to java - env->CallVoidMethod(nat->me, method_onRequestConfirmation, + env->CallVoidMethod(nat->me, method_onRequestPasskeyConfirmation, env->NewStringUTF(object_path), passkey, int(msg)); goto success; + } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "RequestPairingConsent")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestPairingConsent() method", __FUNCTION__); + goto failure; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onRequestPairingConsent, + env->NewStringUTF(object_path), + int(msg)); + goto success; } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Release")) { // reply @@ -992,6 +1029,8 @@ success: #define BOND_RESULT_AUTH_CANCELED 3 #define BOND_RESULT_REMOTE_DEVICE_DOWN 4 #define BOND_RESULT_DISCOVERY_IN_PROGRESS 5 +#define BOND_RESULT_AUTH_TIMEOUT 6 +#define BOND_RESULT_REPEATED_ATTEMPTS 7 void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); @@ -1037,6 +1076,12 @@ void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *n) { !strcmp(err.message, "Discover in progress")) { LOGV("... error = %s (%s)\n", err.name, err.message); result = BOND_RESULT_DISCOVERY_IN_PROGRESS; + } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.RepeatedAttempts")) { + LOGV("... error = %s (%s)\n", err.name, err.message); + result = BOND_RESULT_REPEATED_ATTEMPTS; + } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationTimeout")) { + LOGV("... error = %s (%s)\n", err.name, err.message); + result = BOND_RESULT_AUTH_TIMEOUT; } else { LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); result = BOND_RESULT_ERROR; diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp index de921f1e08fa7..0b71acb5a85a4 100644 --- a/core/jni/android_server_BluetoothService.cpp +++ b/core/jni/android_server_BluetoothService.cpp @@ -455,8 +455,8 @@ static jboolean setPairingConfirmationNative(JNIEnv *env, jobject object, } if (!reply) { - LOGE("%s: Cannot create message reply to RequestConfirmation to " - "D-Bus\n", __FUNCTION__); + LOGE("%s: Cannot create message reply to RequestPasskeyConfirmation or" + "RequestPairingConsent to D-Bus\n", __FUNCTION__); dbus_message_unref(msg); return JNI_FALSE; }