Merge "Add "BT Pairing and Connection Request" retry sequence"
diff --git a/src/com/android/nfc/NfcDispatcher.java b/src/com/android/nfc/NfcDispatcher.java
index 9f70b2e..0a38c59 100644
--- a/src/com/android/nfc/NfcDispatcher.java
+++ b/src/com/android/nfc/NfcDispatcher.java
@@ -628,6 +628,12 @@
if (handover.oobData != null) {
intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_OOB_DATA, handover.oobData);
}
+ if (handover.uuids != null) {
+ intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_UUIDS, handover.uuids);
+ }
+ if (handover.btClass != null) {
+ intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_CLASS, handover.btClass);
+ }
mContext.startServiceAsUser(intent, UserHandle.CURRENT);
return true;
diff --git a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
index 8602ee2..bba4403 100644
--- a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
+++ b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
@@ -18,10 +18,12 @@
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.OobData;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -102,6 +104,8 @@
int mHidResult;
int mRetryCount;
OobData mOobData;
+ boolean mIsHeadsetAvailable;
+ boolean mIsA2dpAvailable;
// protected by mLock
BluetoothA2dp mA2dp;
@@ -113,7 +117,8 @@
}
public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name,
- int transport, OobData oobData, Callback callback) {
+ int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass,
+ Callback callback) {
checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work
mContext = context;
mDevice = device;
@@ -127,6 +132,16 @@
mProvisioning = Settings.Secure.getInt(contentResolver,
Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+ mIsHeadsetAvailable = hasHeadsetCapability(uuids, btClass);
+ mIsA2dpAvailable = hasA2dpCapability(uuids, btClass);
+
+ // Capability information is from NDEF optional field, then it might be empty.
+ // If all capabilities indicate false, try to connect Headset and A2dp just in case.
+ if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
+ mIsHeadsetAvailable = true;
+ mIsA2dpAvailable = true;
+ }
+
mState = STATE_INIT;
}
@@ -212,6 +227,18 @@
Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
mAction = ACTION_DISCONNECT;
} else {
+ // Check if each profile of the device is disabled or not
+ if (mHeadset.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
+ mIsHeadsetAvailable = false;
+ }
+ if (mA2dp.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) {
+ mIsA2dpAvailable = false;
+ }
+ if (!mIsHeadsetAvailable && !mIsA2dpAvailable) {
+ Log.i(TAG, "Both Headset and A2DP profiles are unavailable");
+ complete(false);
+ break;
+ }
Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
mAction = ACTION_CONNECT;
}
@@ -336,14 +363,22 @@
if (mTransport != BluetoothDevice.TRANSPORT_LE) {
if (mHeadset.getConnectionState(mDevice) !=
BluetoothProfile.STATE_CONNECTED) {
- mHfpResult = RESULT_PENDING;
- mHeadset.connect(mDevice);
+ if (mIsHeadsetAvailable) {
+ mHfpResult = RESULT_PENDING;
+ mHeadset.connect(mDevice);
+ } else {
+ mHfpResult = RESULT_DISCONNECTED;
+ }
} else {
mHfpResult = RESULT_CONNECTED;
}
if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
- mA2dpResult = RESULT_PENDING;
- mA2dp.connect(mDevice);
+ if (mIsA2dpAvailable) {
+ mA2dpResult = RESULT_PENDING;
+ mA2dp.connect(mDevice);
+ } else {
+ mA2dpResult = RESULT_DISCONNECTED;
+ }
} else {
mA2dpResult = RESULT_CONNECTED;
}
@@ -513,6 +548,34 @@
mContext.startActivity(dialogIntent);
}
+ boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ for (ParcelUuid uuid : uuids) {
+ if (BluetoothUuid.isAudioSink(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) {
+ return true;
+ }
+ }
+ }
+ if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+ return true;
+ }
+ return false;
+ }
+
+ boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ for (ParcelUuid uuid : uuids) {
+ if (BluetoothUuid.isHandsfree(uuid) || BluetoothUuid.isHeadset(uuid)) {
+ return true;
+ }
+ }
+ }
+ if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ return true;
+ }
+ return false;
+ }
+
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
diff --git a/src/com/android/nfc/handover/HandoverDataParser.java b/src/com/android/nfc/handover/HandoverDataParser.java
index 8973ca5..3408320 100644
--- a/src/com/android/nfc/handover/HandoverDataParser.java
+++ b/src/com/android/nfc/handover/HandoverDataParser.java
@@ -18,6 +18,7 @@
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.nio.charset.Charset;
@@ -25,13 +26,16 @@
import java.util.Random;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
import android.bluetooth.OobData;
import android.content.Context;
import android.content.Intent;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
+import android.os.ParcelUuid;
import android.os.UserHandle;
import android.util.Log;
@@ -60,6 +64,13 @@
private static final int BT_HANDOVER_TYPE_LE_ROLE = 0x1C;
private static final int BT_HANDOVER_TYPE_LONG_LOCAL_NAME = 0x09;
private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08;
+ private static final int BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL = 0x02;
+ private static final int BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE = 0x03;
+ private static final int BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL = 0x04;
+ private static final int BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE = 0x05;
+ private static final int BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL = 0x06;
+ private static final int BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE = 0x07;
+ private static final int BT_HANDOVER_TYPE_CLASS_OF_DEVICE = 0x0D;
private static final int BT_HANDOVER_TYPE_SECURITY_MANAGER_TK = 0x10;
private static final int BT_HANDOVER_TYPE_APPEARANCE = 0x19;
private static final int BT_HANDOVER_TYPE_LE_SC_CONFIRMATION = 0x22;
@@ -70,6 +81,7 @@
public static final int SECURITY_MANAGER_TK_SIZE = 16;
public static final int SECURITY_MANAGER_LE_SC_C_SIZE = 16;
public static final int SECURITY_MANAGER_LE_SC_R_SIZE = 16;
+ private static final int CLASS_OF_DEVICE_SIZE = 3;
private final BluetoothAdapter mBluetoothAdapter;
@@ -85,6 +97,8 @@
public boolean carrierActivating = false;
public int transport = BluetoothDevice.TRANSPORT_AUTO;
public OobData oobData;
+ public ParcelUuid[] uuids = null;
+ public BluetoothClass btClass = null;
}
public static class IncomingHandoverData {
@@ -382,6 +396,7 @@
result.valid = true;
while (payload.remaining() > 0) {
+ boolean success = false;
byte[] nameBytes;
int len = payload.get();
int type = payload.get();
@@ -390,17 +405,41 @@
nameBytes = new byte[len - 1];
payload.get(nameBytes);
result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ success = true;
break;
case BT_HANDOVER_TYPE_LONG_LOCAL_NAME:
if (result.name != null) break; // prefer short name
nameBytes = new byte[len - 1];
payload.get(nameBytes);
result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ success = true;
+ break;
+ case BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE:
+ case BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE:
+ case BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE:
+ result.uuids = parseUuidFromBluetoothRecord(payload, type, len - 1);
+ if (result.uuids != null) {
+ success = true;
+ }
+ break;
+ case BT_HANDOVER_TYPE_CLASS_OF_DEVICE:
+ if (len - 1 != CLASS_OF_DEVICE_SIZE) {
+ Log.i(TAG, "BT OOB: invalid size of Class of Device, should be " +
+ CLASS_OF_DEVICE_SIZE + " bytes.");
+ break;
+ }
+ result.btClass = parseBluetoothClassFromBluetoothRecord(payload);
+ success = true;
break;
default:
- payload.position(payload.position() + len - 1);
break;
}
+ if (!success) {
+ payload.position(payload.position() + len - 1);
+ }
}
} catch (IllegalArgumentException e) {
Log.i(TAG, "BT OOB: invalid BT address");
@@ -533,5 +572,50 @@
return result;
}
-}
+ private ParcelUuid[] parseUuidFromBluetoothRecord(ByteBuffer payload, int type, int len) {
+ int uuidSize;
+ switch (type) {
+ case BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE:
+ uuidSize = BluetoothUuid.UUID_BYTES_16_BIT;
+ break;
+ case BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE:
+ uuidSize = BluetoothUuid.UUID_BYTES_32_BIT;
+ break;
+ case BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL:
+ case BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE:
+ uuidSize = BluetoothUuid.UUID_BYTES_128_BIT;
+ break;
+ default:
+ Log.i(TAG, "BT OOB: invalid size of UUID");
+ return null;
+ }
+
+ if (len == 0 || len % uuidSize != 0) {
+ Log.i(TAG, "BT OOB: invalid size of UUIDs, should be multiples of UUID bytes length");
+ return null;
+ }
+
+ int num = len / uuidSize;
+ ParcelUuid[] uuids = new ParcelUuid[num];
+ byte[] data = new byte[uuidSize];
+ for (int i = 0; i < num; i++) {
+ payload.get(data);
+ uuids[i] = BluetoothUuid.parseUuidFrom(data);
+ }
+ return uuids;
+ }
+
+ private BluetoothClass parseBluetoothClassFromBluetoothRecord(ByteBuffer payload) {
+ byte[] btClass = new byte[CLASS_OF_DEVICE_SIZE];
+ payload.get(btClass);
+
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
+ buffer.put(btClass);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ return new BluetoothClass(buffer.getInt(0));
+ }
+}
diff --git a/src/com/android/nfc/handover/PeripheralHandoverService.java b/src/com/android/nfc/handover/PeripheralHandoverService.java
index 95bf0d6..06b1843 100644
--- a/src/com/android/nfc/handover/PeripheralHandoverService.java
+++ b/src/com/android/nfc/handover/PeripheralHandoverService.java
@@ -18,6 +18,7 @@
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.OobData;
import android.content.BroadcastReceiver;
@@ -32,6 +33,8 @@
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Log;
@@ -48,6 +51,8 @@
public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
+ public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
+ public static final String EXTRA_PERIPHERAL_CLASS = "class";
// Amount of time to pause polling when connecting to peripherals
private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
@@ -160,9 +165,19 @@
String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
+ Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS);
+ BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS);
+
+ ParcelUuid[] uuids = null;
+ if (parcelables != null) {
+ uuids = new ParcelUuid[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ uuids[i] = (ParcelUuid)parcelables[i];
+ }
+ }
mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
- this, device, name, transport, oobData, this);
+ this, device, name, transport, oobData, uuids, btClass, this);
if (transport == BluetoothDevice.TRANSPORT_LE) {
mHandler.sendMessageDelayed(