Merge "bt: Add late bonding confirmation mechanism" into udc-dev
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 1aa1741..2e6bb53 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -47,6 +47,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -78,6 +79,7 @@
     BluetoothDevice mDevice;
     private HearingAidInfo mHearingAidInfo;
     private int mGroupId;
+    private Timestamp mBondTimestamp;
 
     // Need this since there is no method for getting RSSI
     short mRssi;
@@ -889,15 +891,25 @@
             mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
             mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
             mDevice.setSimAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
+
+            mBondTimestamp = null;
         }
 
         refresh();
 
-        if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
-            connect();
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            mBondTimestamp = new Timestamp(System.currentTimeMillis());
+
+            if (mDevice.isBondingInitiatedLocally()) {
+                connect();
+            }
         }
     }
 
+    public Timestamp getBondTimestamp() {
+        return mBondTimestamp;
+    }
+
     public BluetoothClass getBtClass() {
         return mDevice.getBluetoothClass();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index d55144e..0db88af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -37,6 +38,8 @@
     private static final String TAG = "CachedBluetoothDeviceManager";
     private static final boolean DEBUG = BluetoothUtils.D;
 
+    @VisibleForTesting static int sLateBondingTimeoutMillis = 5000; // 5s
+
     private Context mContext;
     private final LocalBluetoothManager mBtManager;
 
@@ -47,6 +50,7 @@
     @VisibleForTesting
     CsipDeviceManager mCsipDeviceManager;
     BluetoothDevice mOngoingSetMemberPair;
+    boolean mIsLateBonding;
 
     public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
@@ -309,6 +313,7 @@
 
             // To clear the SetMemberPair flag when the Bluetooth is turning off.
             mOngoingSetMemberPair = null;
+            mIsLateBonding = false;
         }
     }
 
@@ -377,15 +382,53 @@
     private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
         boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
         int bondState = device.getBondState();
-        if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
-                || !mCsipDeviceManager.isExistedGroupId(groupId)) {
-            Log.d(TAG, "isOngoingSetMemberPair: " + isOngoingSetMemberPair
-                    + " , device.getBondState: " + bondState);
+        boolean groupExists = mCsipDeviceManager.isExistedGroupId(groupId);
+        Log.d(TAG,
+                "isOngoingSetMemberPair=" + isOngoingSetMemberPair + ", bondState=" + bondState
+                        + ", groupExists=" + groupExists + ", groupId=" + groupId);
+
+        if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE || !groupExists) {
             return false;
         }
         return true;
     }
 
+    private synchronized boolean checkLateBonding(int groupId) {
+        CachedBluetoothDevice firstDevice = mCsipDeviceManager.getFirstMemberDevice(groupId);
+        if (firstDevice == null) {
+            Log.d(TAG, "No first device in group: " + groupId);
+            return false;
+        }
+
+        Timestamp then = firstDevice.getBondTimestamp();
+        if (then == null) {
+            Log.d(TAG, "No bond timestamp");
+            return true;
+        }
+
+        Timestamp now = new Timestamp(System.currentTimeMillis());
+
+        long diff = (now.getTime() - then.getTime());
+        Log.d(TAG, "Time difference to first bonding: " + diff + "ms");
+
+        return diff > sLateBondingTimeoutMillis;
+    }
+
+    /**
+     * Called to check if there is an ongoing bonding for the device and it is late bonding.
+     * If the device is not matching the ongoing bonding device then false will be returned.
+     *
+     * @param device The device to check.
+     */
+    public synchronized boolean isLateBonding(BluetoothDevice device) {
+        if (!isOngoingPairByCsip(device)) {
+            Log.d(TAG, "isLateBonding: pair not ongoing or not matching device");
+            return false;
+        }
+
+        return mIsLateBonding;
+    }
+
     /**
      * Called when we found a set member of a group. The function will check the {@code groupId} if
      * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
@@ -398,12 +441,14 @@
         if (!shouldPairByCsip(device, groupId)) {
             return;
         }
-        Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
+        Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " groupId=" + groupId + " by CSIP ");
         mOngoingSetMemberPair = device;
+        mIsLateBonding = checkLateBonding(groupId);
         syncConfigFromMainDevice(device, groupId);
         if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
             Log.d(TAG, "Bonding could not be started");
             mOngoingSetMemberPair = null;
+            mIsLateBonding = false;
         }
     }
 
@@ -439,7 +484,7 @@
      * function, and would not like to update the UI. If not, return {@code false}.
      */
     public synchronized boolean onBondStateChangedIfProcess(BluetoothDevice device, int bondState) {
-        if (mOngoingSetMemberPair == null || !mOngoingSetMemberPair.equals(device)) {
+        if (!isOngoingPairByCsip(device)) {
             return false;
         }
 
@@ -448,6 +493,7 @@
         }
 
         mOngoingSetMemberPair = null;
+        mIsLateBonding = false;
         if (bondState != BluetoothDevice.BOND_NONE) {
             if (findDevice(device) == null) {
                 final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
@@ -471,7 +517,7 @@
      * {@code false}.
      */
     public boolean isOngoingPairByCsip(BluetoothDevice device) {
-        return !(mOngoingSetMemberPair == null) && mOngoingSetMemberPair.equals(device);
+        return mOngoingSetMemberPair != null && mOngoingSetMemberPair.equals(device);
     }
 
     private void log(String msg) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 8269b56..3a6da2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -241,6 +241,17 @@
         return groupDevicesList;
     }
 
+    public CachedBluetoothDevice getFirstMemberDevice(int groupId) {
+        List<CachedBluetoothDevice> members = getGroupDevicesFromAllOfDevicesList(groupId);
+        if (members.isEmpty())
+            return null;
+
+        CachedBluetoothDevice firstMember = members.get(0);
+        log("getFirstMemberDevice: groupId=" + groupId
+                + " address=" + firstMember.getDevice().getAnonymizedAddress());
+        return firstMember;
+    }
+
     @VisibleForTesting
     CachedBluetoothDevice getPreferredMainDevice(int groupId,
             List<CachedBluetoothDevice> groupDevicesList) {