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(