Merge "Add More Unit Test for FastPairDualConnection." into tm-dev
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
index a7c05d6..c963aa6 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
@@ -18,7 +18,6 @@
 
 import android.annotation.WorkerThread;
 import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
 
 import androidx.annotation.Nullable;
 import androidx.core.util.Consumer;
@@ -135,13 +134,6 @@
     @Nullable
     public abstract String getPublicAddress();
 
-    /**
-     * Creates cloud syncing intent which saves the Fast Pair device to the account.
-     *
-     * @param accountKey account key which is written into the Fast Pair device
-     * @return cloud syncing intent
-     */
-    public abstract Intent createCloudSyncingIntent(byte[] accountKey);
 
     /** Callback for getting notifications when pairing has completed. */
     public interface OnPairedCallback {
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
index 8b466fa..789ef59 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
@@ -20,7 +20,6 @@
 import static android.bluetooth.BluetoothDevice.BOND_BONDING;
 import static android.bluetooth.BluetoothDevice.BOND_NONE;
 
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
@@ -28,7 +27,6 @@
 import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Strings.isNullOrEmpty;
 import static com.google.common.base.Verify.verifyNotNull;
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.primitives.Bytes.concat;
@@ -221,6 +219,12 @@
                     },
                     REQUESTED_SERVICES_LTV);
 
+    private static boolean sTestMode = false;
+
+    static void enableTestMode() {
+        sTestMode = true;
+    }
+
     /**
      * Operation Result Code.
      */
@@ -493,6 +497,7 @@
         // Lazily initialize a new connection manager for each pairing request.
         initGattConnectionManager();
         boolean isSecretHandshakeCompleted = true;
+
         try {
             if (key != null && key.length > 0) {
                 // GATT_CONNECTION_AND_SECRET_HANDSHAKE start.
@@ -731,11 +736,6 @@
         }
     }
 
-    @VisibleForTesting
-    void setBeforeDirectlyConnectProfileFromCacheForTest(Runnable runnable) {
-        this.mBeforeDirectlyConnectProfileFromCacheForTest = runnable;
-    }
-
     /**
      * Logs for user retry, check go/fastpairquality21q3 for more details.
      */
@@ -898,6 +898,7 @@
                                     mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(),
                                     mPreferences.getNumSdpAttempts()));
         }
+
         BluetoothDevice device =
                 mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress)
                         .unwrap();
@@ -910,6 +911,7 @@
                         ? null
                         : new KeyBasedPairingInfo(
                                 mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding);
+
         BluetoothAudioPairer pairer =
                 new BluetoothAudioPairer(
                         mContext,
@@ -928,7 +930,9 @@
         // normally do and we can finish early. It is also more reliable than tearing down the bond
         // and recreating it.
         try {
-            attemptDirectConnectionIfBonded(device, pairer);
+            if (!sTestMode) {
+                attemptDirectConnectionIfBonded(device, pairer);
+            }
             callbackOnPaired();
             return maybeWriteAccountKey(device);
         } catch (PairingException e) {
@@ -1299,26 +1303,6 @@
     }
 
     /**
-     * Creates cloud syncing intent which saves the Fast Pair device to the account.
-     *
-     * @param accountKey account key which is written into the Fast Pair device
-     * @return cloud syncing intent
-     */
-    public Intent createCloudSyncingIntent(byte[] accountKey) {
-        Intent intent = new Intent(BroadcastConstants.ACTION_FAST_PAIR_DEVICE_ADDED);
-        intent.setClassName(BroadcastConstants.PACKAGE_NAME, BroadcastConstants.SERVICE_NAME);
-        intent.putExtra(BroadcastConstants.EXTRA_ADDRESS, mBleAddress);
-        if (mPublicAddress != null) {
-            intent.putExtra(BroadcastConstants.EXTRA_PUBLIC_ADDRESS, mPublicAddress);
-        }
-        intent.putExtra(BroadcastConstants.EXTRA_ACCOUNT_KEY, accountKey);
-        intent.putExtra(
-                BroadcastConstants.EXTRA_RETROACTIVE_PAIR, mPreferences.getIsRetroactivePairing());
-
-        return intent;
-    }
-
-    /**
      * Checks whether or not an account key should be written to the device and writes it if so.
      * This is called after handle notifying the pairedCallback that we've finished pairing, because
      * at this point the headset is ready to use.
@@ -1328,7 +1312,9 @@
             throws InterruptedException, ExecutionException, TimeoutException,
             NoSuchAlgorithmException,
             BluetoothException {
-        Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+        if (!sTestMode) {
+            Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+        }
         if (!shouldWriteAccountKey()) {
             // For FastPair 2.0, here should be a subsequent pairing case.
             return null;
@@ -1611,95 +1597,6 @@
         waitForBluetoothStateUsingPolling(state);
     }
 
-    /**
-     * Update device name to provider.
-     *
-     * <pre>
-     *    A) Connect GATT
-     *    B) Handshake with provider to get the pairing secret and public address
-     *    C) Write new device name into provider through name characteristic in GATT
-     *    D) Disconnect GATT
-     * </pre>
-     *
-     * Synchronous: Blocks until until the name has finished being written. Throws on any error.
-     *
-     * @param key is a 16-byte account key. See go/fast-pair-2-spec for how these keys are used.
-     * @return true if the task is done, i.e. name is written successfully or it is skipped because
-     * of unsupported Name characteristic, false if some error happens and may need to re-try.
-     */
-    @WorkerThread
-    public boolean updateProviderName(@Nullable byte[] key, @Nullable String deviceName)
-            throws BluetoothException, InterruptedException, TimeoutException, ExecutionException,
-            PairingException, GeneralSecurityException {
-        if (!mPreferences.getEnableNamingCharacteristic()) {
-            Log.i(TAG, "Disable NamingCharacteristic feature, ignoring.");
-            return false;
-        }
-        if (isNullOrEmpty(deviceName)) {
-            Log.i(TAG, "Provider name is null or empty, ignoring.");
-            return false;
-        }
-        if (key == null || key.length != AES_BLOCK_LENGTH) {
-            Log.i(TAG, "key is null or key length is not account key size.");
-            return false;
-        }
-
-        Log.i(TAG, "Start to update device name for provider.");
-        boolean result = false;
-        if (mPreferences.getExtraLoggingInformation() != null) {
-            this.mEventLogger.bind(
-                    mContext, mBleAddress, mPreferences.getExtraLoggingInformation());
-        }
-
-        // Lazily initialize a new connection manager for each renaming request.
-        mGattConnectionManager =
-                new GattConnectionManager(
-                        mContext,
-                        mPreferences,
-                        mEventLogger,
-                        mBluetoothAdapter,
-                        this::toggleBluetooth,
-                        mBleAddress,
-                        mTimingLogger,
-                        mFastPairSignalChecker,
-                        /* setMtu= */ true);
-
-        try (BluetoothGattConnection connection = mGattConnectionManager.getConnection()) {
-            UUID characteristicUuid = NameCharacteristic.getId(connection);
-            if (!validateBluetoothGattCharacteristic(connection, characteristicUuid)) {
-                Log.i(TAG, "Can't find name characteristic, skip to write name with retry times.");
-                mGattConnectionManager.closeConnection();
-                // Returns true because the task is done with no name characteristic in device.
-                return true;
-            }
-            mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE);
-            // Handshake to get pairing secret for name characteristic decryption and encryption.
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Handshake")) {
-                handshakeForActionOverBle(key, AdditionalDataType.PERSONALIZED_NAME);
-            }
-            mEventLogger.logCurrentEventSucceeded();
-            // After handshake to get secret, write the name back to provider.
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                    "WriteNameToProvider")) {
-                result = writeNameToProvider(deviceName, mPublicAddress);
-            }
-        } catch (BluetoothException
-                | InterruptedException
-                | TimeoutException
-                | ExecutionException
-                | PairingException
-                | GeneralSecurityException e) {
-            if (mEventLogger.isCurrentEvent()) {
-                mEventLogger.logCurrentEventFailed(e);
-            }
-            throw e;
-        } finally {
-            mTimingLogger.dump();
-        }
-        mGattConnectionManager.closeConnection();
-        return result;
-    }
-
     private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException {
         // There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF.
         // So poll instead.
@@ -1882,9 +1779,13 @@
                             /* setMtu= */ true);
             mGattConnectionManager.closeConnection();
         }
+        if (sTestMode) {
+            return null;
+        }
         BluetoothGattConnection connection = mGattConnectionManager.getConnection();
         connection.setOperationTimeout(
                 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
+
         try {
             String firmwareVersion =
                     new String(
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
index 44bab71..a103a72 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.Mockito.doNothing;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.platform.test.annotations.Presubmit;
 
@@ -38,7 +39,9 @@
 import com.android.server.nearby.common.bluetooth.BluetoothGattException;
 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.protobuf.ByteString;
 
 import junit.framework.TestCase;
 
@@ -47,6 +50,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 
 /**
@@ -56,11 +61,16 @@
 @SmallTest
 public class FastPairDualConnectionTest extends TestCase {
 
-    private static final String BLE_ADDRESS = "BLE_ADDRESS";
+    private static final String BLE_ADDRESS = "00:11:22:33:FF:EE";
     private static final String MASKED_BLE_ADDRESS = "MASKED_BLE_ADDRESS";
     private static final short[] PROFILES = {Constants.A2DP_SINK_SERVICE_UUID};
     private static final int NUM_CONNECTION_ATTEMPTS = 1;
     private static final boolean ENABLE_PAIRING_BEHAVIOR = true;
+    private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
+            .getRemoteDevice("11:22:33:44:55:66");
+    private static final String DEVICE_NAME = "DEVICE_NAME";
+    private static final byte[] ACCOUNT_KEY = new byte[]{1, 3};
+    private static final byte[] HASH_VALUE = new byte[]{7};
 
     private TestEventLogger mEventLogger;
     @Mock private TimingLogger mTimingLogger;
@@ -70,6 +80,8 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        BluetoothAudioPairer.enableTestMode();
+        FastPairDualConnection.enableTestMode();
         MockitoAnnotations.initMocks(this);
 
         doNothing().when(mBluetoothAudioPairer).connect(anyShort(), anyBoolean());
@@ -127,7 +139,7 @@
 
 
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void appendMoreErrorCode_gattError() {
+    public void testAppendMoreErrorCode_gattError() {
         assertThat(
                 appendMoreErrorCode(
                         GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
@@ -170,6 +182,131 @@
                 .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST);
     }
 
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testUnpairNotCrash() {
+        try {
+            new FastPairDualConnection(
+                    ApplicationProvider.getApplicationContext(),
+                    BLE_ADDRESS,
+                    Preferences.builder().build(),
+                    mEventLogger,
+                    mTimingLogger).unpair(BLUETOOTH_DEVICE);
+        } catch (ExecutionException | InterruptedException | ReflectionException
+                | TimeoutException | PairingException e) {
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetFastPairHistory() {
+        new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().build(),
+                mEventLogger,
+                mTimingLogger).setFastPairHistory(ImmutableList.of());
+    }
+
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetProviderDeviceName() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().build(),
+                mEventLogger,
+                mTimingLogger);
+        connection.setProviderDeviceName(DEVICE_NAME);
+        connection.getProviderDeviceName();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetExistingAccountKey() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().build(),
+                mEventLogger,
+                mTimingLogger);
+        connection.getExistingAccountKey();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testPair() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().setNumSdpAttempts(0)
+                        .setLogPairWithCachedModelId(false).build(),
+                mEventLogger,
+                mTimingLogger);
+        try {
+            connection.pair();
+        } catch (BluetoothException | InterruptedException | ReflectionException
+                | ExecutionException | TimeoutException | PairingException e) {
+        }
+    }
+
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetPublicAddress() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().setNumSdpAttempts(0)
+                        .setLogPairWithCachedModelId(false).build(),
+                mEventLogger,
+                mTimingLogger);
+        connection.getPublicAddress();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testShouldWriteAccountKeyForExistingCase() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().setNumSdpAttempts(0)
+                        .setLogPairWithCachedModelId(false).build(),
+                mEventLogger,
+                mTimingLogger);
+        connection.shouldWriteAccountKeyForExistingCase(ACCOUNT_KEY);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReadFirmwareVersion() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().setNumSdpAttempts(0)
+                        .setLogPairWithCachedModelId(false).build(),
+                mEventLogger,
+                mTimingLogger);
+        try {
+            connection.readFirmwareVersion();
+        } catch (BluetoothException | InterruptedException | ExecutionException
+                | TimeoutException e) {
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHistoryItem() {
+        FastPairDualConnection connection = new FastPairDualConnection(
+                ApplicationProvider.getApplicationContext(),
+                BLE_ADDRESS,
+                Preferences.builder().setNumSdpAttempts(0)
+                        .setLogPairWithCachedModelId(false).build(),
+                mEventLogger,
+                mTimingLogger);
+        ImmutableList.Builder<FastPairHistoryItem> historyBuilder = ImmutableList.builder();
+        FastPairHistoryItem historyItem1 =
+                FastPairHistoryItem.create(
+                        ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(HASH_VALUE));
+        historyBuilder.add(historyItem1);
+
+        connection.setFastPairHistory(historyBuilder.build());
+        assertThat(connection.mPairedHistoryFinder.isInPairedHistory("11:22:33:44:55:88"))
+                .isFalse();
+    }
+
     static class TestEventLogger implements EventLogger {
 
         private List<Item> mLogs = new ArrayList<>();