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<>();