Merge "Add Reflect, reflectionException."
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java
new file mode 100644
index 0000000..28a9c33
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.generateKey;
+
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
+
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * This is to generate account key with fast-pair style.
+ */
+public final class AccountKeyGenerator {
+
+ // Generate a key where the first byte is always defined as the type, 0x04. This maintains 15
+ // bytes of entropy in the key while also allowing providers to verify that they have received
+ // a properly formatted key and decrypted it correctly, minimizing the risk of replay attacks.
+
+ /**
+ * Creates account key.
+ */
+ public static byte[] createAccountKey() throws NoSuchAlgorithmException {
+ byte[] accountKey = generateKey();
+ accountKey[0] = AccountKeyCharacteristic.TYPE;
+ return accountKey;
+ }
+
+ private AccountKeyGenerator() {
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java
new file mode 100644
index 0000000..6c467d3
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDING;
+import static android.bluetooth.BluetoothDevice.BOND_NONE;
+import static android.bluetooth.BluetoothDevice.ERROR;
+import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+
+import com.google.common.base.Strings;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Pairs to Bluetooth classic devices with passkey confirmation.
+ */
+// TODO(b/202524672): Add class unit test.
+public class BluetoothClassicPairer {
+
+ private static final String TAG = BluetoothClassicPairer.class.getSimpleName();
+ /**
+ * Hidden, see {@link BluetoothDevice}.
+ */
+ private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
+
+ private final Context mContext;
+ private final BluetoothDevice mDevice;
+ private final Preferences mPreferences;
+ private final PasskeyConfirmationHandler mPasskeyConfirmationHandler;
+
+ public BluetoothClassicPairer(
+ Context context,
+ BluetoothDevice device,
+ Preferences preferences,
+ PasskeyConfirmationHandler passkeyConfirmationHandler) {
+ this.mContext = context;
+ this.mDevice = device;
+ this.mPreferences = preferences;
+ this.mPasskeyConfirmationHandler = passkeyConfirmationHandler;
+ }
+
+ /**
+ * Pairs with the device. Throws a {@link PairingException} if any error occurs.
+ */
+ @WorkerThread
+ public void pair() throws PairingException {
+ Log.i(TAG, "BluetoothClassicPairer, createBond with " + maskBluetoothAddress(mDevice)
+ + ", type=" + mDevice.getType());
+ try (BondedReceiver bondedReceiver = new BondedReceiver()) {
+ if (mDevice.createBond()) {
+ bondedReceiver.await(mPreferences.getCreateBondTimeoutSeconds(), SECONDS);
+ } else {
+ throw new PairingException(
+ "BluetoothClassicPairer, createBond got immediate error");
+ }
+ } catch (TimeoutException | InterruptedException | ExecutionException e) {
+ throw new PairingException("BluetoothClassicPairer, createBond failed", e);
+ }
+ }
+
+ protected boolean isPaired() {
+ return mDevice.getBondState() == BOND_BONDED;
+ }
+
+ /**
+ * Receiver that closes after bonding has completed.
+ */
+ private class BondedReceiver extends DeviceIntentReceiver {
+
+ private BondedReceiver() {
+ super(
+ mContext,
+ mPreferences,
+ mDevice,
+ BluetoothDevice.ACTION_PAIRING_REQUEST,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ }
+
+ /**
+ * Called with ACTION_PAIRING_REQUEST and ACTION_BOND_STATE_CHANGED about the interesting
+ * device (see {@link DeviceIntentReceiver}).
+ *
+ * <p>The ACTION_PAIRING_REQUEST intent provides the passkey which will be sent to the
+ * {@link PasskeyConfirmationHandler} for showing the UI, and the ACTION_BOND_STATE_CHANGED
+ * will provide the result of the bonding.
+ */
+ @Override
+ protected void onReceiveDeviceIntent(Intent intent) {
+ String intentAction = intent.getAction();
+ BluetoothDevice remoteDevice = intent.getParcelableExtra(EXTRA_DEVICE);
+ if (Strings.isNullOrEmpty(intentAction)
+ || remoteDevice == null
+ || !remoteDevice.getAddress().equals(mDevice.getAddress())) {
+ Log.w(TAG,
+ "BluetoothClassicPairer, receives " + intentAction
+ + " from unexpected device " + maskBluetoothAddress(remoteDevice));
+ return;
+ }
+ switch (intentAction) {
+ case BluetoothDevice.ACTION_PAIRING_REQUEST:
+ handlePairingRequest(
+ remoteDevice,
+ intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR),
+ intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR));
+ break;
+ case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+ handleBondStateChanged(
+ intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, ERROR),
+ intent.getIntExtra(EXTRA_REASON, ERROR));
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handlePairingRequest(BluetoothDevice device, int variant, int passkey) {
+ Log.i(TAG,
+ "BluetoothClassicPairer, pairing request, " + device + ", " + variant + ", "
+ + passkey);
+ // Prevent Bluetooth Settings from getting the pairing request and showing its own UI.
+ abortBroadcast();
+ mPasskeyConfirmationHandler.onPasskeyConfirmation(device, passkey);
+ }
+
+ private void handleBondStateChanged(int bondState, int reason) {
+ Log.i(TAG,
+ "BluetoothClassicPairer, bond state changed to " + bondState + ", reason="
+ + reason);
+ switch (bondState) {
+ case BOND_BONDING:
+ // Don't close!
+ return;
+ case BOND_BONDED:
+ close();
+ return;
+ case BOND_NONE:
+ default:
+ closeWithError(
+ new PairingException(
+ "BluetoothClassicPairer, createBond failed, reason:" + reason));
+ }
+ }
+ }
+
+ // Applies UsesPermission annotation will create circular dependency.
+ @SuppressLint("MissingPermission")
+ static void setPairingConfirmation(BluetoothDevice device, boolean confirm) {
+ Log.i(TAG, "BluetoothClassicPairer: setPairingConfirmation " + maskBluetoothAddress(device)
+ + ", confirm: " + confirm);
+ device.setPairingConfirmation(confirm);
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java
new file mode 100644
index 0000000..d3eb388
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static android.bluetooth.BluetoothProfile.A2DP;
+import static android.bluetooth.BluetoothProfile.HEADSET;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.toFastPair128BitUuid;
+
+import static com.google.common.primitives.Bytes.concat;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothHeadset;
+import android.util.Log;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.primitives.Shorts;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * Fast Pair and Transport Discovery Service constants.
+ *
+ * <p>Unless otherwise specified, these numbers come from
+ * {https://www.bluetooth.com/specifications/gatt}.
+ */
+public final class Constants {
+
+ /** A2DP sink service uuid. */
+ public static final short A2DP_SINK_SERVICE_UUID = 0x110B;
+
+ /** Headset service uuid. */
+ public static final short HEADSET_SERVICE_UUID = 0x1108;
+
+ /** Hands free sink service uuid. */
+ public static final short HANDS_FREE_SERVICE_UUID = 0x111E;
+
+ /** Bluetooth address length. */
+ public static final int BLUETOOTH_ADDRESS_LENGTH = 6;
+
+ private static final String TAG = Constants.class.getSimpleName();
+
+ /**
+ * Defined by https://developers.google.com/nearby/fast-pair/spec.
+ */
+ public static final class FastPairService {
+
+ /** Fast Pair service UUID. */
+ public static final UUID ID = to128BitUuid((short) 0xFE2C);
+
+ /**
+ * Characteristic to write verification bytes to during the key handshake.
+ */
+ public static final class KeyBasedPairingCharacteristic {
+
+ private static final short SHORT_UUID = 0x1234;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * KeyBasedPairingCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore needs the {@link BluetoothGattConnection} parameter to check the supported
+ * status of the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ /**
+ * Constants related to the decrypted request written to this characteristic.
+ */
+ public static final class Request {
+
+ /**
+ * The size of this message.
+ */
+ public static final int SIZE = 16;
+
+ /**
+ * The index of this message for indicating the type byte.
+ */
+ public static final int TYPE_INDEX = 0;
+
+ /**
+ * The index of this message for indicating the flags byte.
+ */
+ public static final int FLAGS_INDEX = 1;
+
+ /**
+ * The index of this message for indicating the verification data start from.
+ */
+ public static final int VERIFICATION_DATA_INDEX = 2;
+
+ /**
+ * The length of verification data, it is Provider’s current BLE address or public
+ * address.
+ */
+ public static final int VERIFICATION_DATA_LENGTH = BLUETOOTH_ADDRESS_LENGTH;
+
+ /**
+ * The index of this message for indicating the seeker's public address start from.
+ */
+ public static final int SEEKER_PUBLIC_ADDRESS_INDEX = 8;
+
+ /**
+ * The index of this message for indicating event group.
+ */
+ public static final int EVENT_GROUP_INDEX = 8;
+
+ /**
+ * The index of this message for indicating event code.
+ */
+ public static final int EVENT_CODE_INDEX = 9;
+
+ /**
+ * The index of this message for indicating the length of additional data of the
+ * event.
+ */
+ public static final int EVENT_ADDITIONAL_DATA_LENGTH_INDEX = 10;
+
+ /**
+ * The index of this message for indicating the event additional data start from.
+ */
+ public static final int EVENT_ADDITIONAL_DATA_INDEX = 11;
+
+ /**
+ * The index of this message for indicating the additional data type used in the
+ * following Additional Data characteristic.
+ */
+ public static final int ADDITIONAL_DATA_TYPE_INDEX = 10;
+
+ /**
+ * The type of this message for Key-based Pairing Request.
+ */
+ public static final byte TYPE_KEY_BASED_PAIRING_REQUEST = 0x00;
+
+ /**
+ * The bit indicating that the Fast Pair device should temporarily become
+ * discoverable.
+ */
+ public static final byte REQUEST_DISCOVERABLE = (byte) (1 << 7);
+
+ /**
+ * The bit indicating that the requester (Seeker) has included their public address
+ * in bytes [7,12] of the request, and the Provider should initiate bonding to that
+ * address.
+ */
+ public static final byte PROVIDER_INITIATES_BONDING = (byte) (1 << 6);
+
+ /**
+ * The bit indicating that Seeker requests Provider shall return the existing name.
+ */
+ public static final byte REQUEST_DEVICE_NAME = (byte) (1 << 5);
+
+ /**
+ * The bit to request retroactive pairing.
+ */
+ public static final byte REQUEST_RETROACTIVE_PAIR = (byte) (1 << 4);
+
+ /**
+ * The type of this message for action over BLE.
+ */
+ public static final byte TYPE_ACTION_OVER_BLE = 0x10;
+
+ private Request() {
+ }
+ }
+
+ /**
+ * Enumerates all flags of key-based pairing request.
+ */
+ // TODO(b/201673262): Convert enum to byte.
+ public enum KeyBasedPairingRequestFlag {
+ /**
+ * The bit indicating that the Fast Pair device should temporarily become
+ * discoverable.
+ */
+ REQUEST_DISCOVERABLE((byte) (1 << 7)),
+ /**
+ * The bit indicating that the requester (Seeker) has included their public address
+ * in bytes [7,12] of the request, and the Provider should initiate bonding to that
+ * address.
+ */
+ PROVIDER_INITIATES_BONDING((byte) (1 << 6)),
+ /**
+ * The bit indicating that Seeker requests Provider shall return the existing name.
+ */
+ REQUEST_DEVICE_NAME((byte) (1 << 5)),
+ /**
+ * The bit indicating that the Seeker request retroactive pairing.
+ */
+ REQUEST_RETROACTIVE_PAIR((byte) (1 << 4));
+
+ private final byte mValue;
+
+ KeyBasedPairingRequestFlag(byte flag) {
+ this.mValue = flag;
+ }
+
+ /** Returns value. */
+ public byte getValue() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Enumerates all flags of action over BLE request, see Fast Pair spec for details.
+ */
+ // TODO(b/201673262): Convert enum to byte.
+ public enum ActionOverBleFlag {
+ /**
+ * The bit indicating that the handshaking is for Device Action.
+ */
+ DEVICE_ACTION((byte) (1 << 7)),
+ /**
+ * The bit indicating that this handshake will be followed by Additional Data
+ * characteristic.
+ */
+ ADDITIONAL_DATA_CHARACTERISTIC((byte) (1 << 6));
+
+ private final byte mValue;
+
+ ActionOverBleFlag(byte value) {
+ this.mValue = value;
+ }
+
+ /** Retunns value. */
+ public byte getValue() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Constants related to the decrypted response sent back in a notify.
+ */
+ public static final class Response {
+
+ /**
+ * The type of this message = Key-based Pairing Response.
+ */
+ public static final byte TYPE = 0x01;
+
+ private Response() {
+ }
+ }
+
+ private KeyBasedPairingCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic used during Key-based Pairing, to exchange the encrypted passkey.
+ */
+ public static final class PasskeyCharacteristic {
+
+ private static final short SHORT_UUID = 0x1235;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * PasskeyCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of
+ * the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ /**
+ * The type of the Passkey Block message.
+ */
+ // TODO(b/201673262): Convert enum to byte.
+ public enum Type {
+ /**
+ * Seeker's Passkey.
+ */
+ SEEKER((byte) 0x02),
+ /**
+ * Provider's Passkey.
+ */
+ PROVIDER((byte) 0x03);
+
+ private final byte mValue;
+
+ Type(byte value) {
+ this.mValue = value;
+ }
+ }
+
+ /**
+ * Constructs the encrypted value to write to the characteristic.
+ */
+ public static byte[] encrypt(Type type, byte[] secret, int passkey)
+ throws GeneralSecurityException {
+ Preconditions.checkArgument(
+ 0 < passkey && passkey < /*2^24=*/ 16777216,
+ "Passkey %s must be positive and fit in 3 bytes",
+ passkey);
+ byte[] passkeyBytes =
+ new byte[]{(byte) (passkey >>> 16), (byte) (passkey >>> 8), (byte) passkey};
+ byte[] salt =
+ new byte[AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH - 1
+ - passkeyBytes.length];
+ new Random().nextBytes(salt);
+ return AesEcbSingleBlockEncryption.encrypt(
+ secret, concat(new byte[]{type.mValue}, passkeyBytes, salt));
+ }
+
+ /**
+ * Extracts the passkey from the encrypted characteristic value.
+ */
+ public static int decrypt(Type type, byte[] secret, byte[] passkeyCharacteristicValue)
+ throws GeneralSecurityException {
+ byte[] decrypted = AesEcbSingleBlockEncryption
+ .decrypt(secret, passkeyCharacteristicValue);
+ if (decrypted[0] != type.mValue) {
+ throw new GeneralSecurityException(
+ "Wrong Passkey Block type (expected " + type.mValue + ", got "
+ + decrypted[0] + ")");
+ }
+ return ByteBuffer.allocate(4)
+ .put((byte) 0)
+ .put(decrypted, /*offset=*/ 1, /*length=*/ 3)
+ .getInt(0);
+ }
+
+ private PasskeyCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic to write to during the key exchange.
+ */
+ public static final class AccountKeyCharacteristic {
+
+ private static final short SHORT_UUID = 0x1236;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * AccountKeyCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of
+ * the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ /**
+ * The type for this message, account key request.
+ */
+ public static final byte TYPE = 0x04;
+
+ private AccountKeyCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic to write to and notify on for handling personalized name, see {@link
+ * NamingEncoder}.
+ */
+ public static final class NameCharacteristic {
+
+ private static final short SHORT_UUID = 0x1237;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * NameCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of
+ * the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ private NameCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic to write to and notify on for handling additional data, see
+ * https://developers.google.com/nearby/fast-pair/early-access/spec#AdditionalData
+ */
+ public static final class AdditionalDataCharacteristic {
+
+ private static final short SHORT_UUID = 0x1237;
+
+ public static final int DATA_ID_INDEX = 0;
+ public static final int DATA_LENGTH_INDEX = 1;
+ public static final int DATA_START_INDEX = 2;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * AdditionalDataCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of
+ * the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ /**
+ * Enumerates all types of additional data.
+ */
+ // TODO(b/201673262): Convert enum to byte.
+ public enum AdditionalDataType {
+ /**
+ * The value indicating that the type is for personalized name.
+ */
+ PERSONALIZED_NAME((byte) 0x01),
+ UNKNOWN((byte) 0x00); // and all others.
+
+ private final byte mValue;
+
+ AdditionalDataType(byte value) {
+ this.mValue = value;
+ }
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ /** Converts byte to enum. */
+ public static AdditionalDataType valueOf(byte value) {
+ for (AdditionalDataType type : AdditionalDataType.values()) {
+ if (type.getValue() == value) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+ }
+
+ private AdditionalDataCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic to control the beaconing feature (FastPair+Eddystone).
+ */
+ public static final class BeaconActionsCharacteristic {
+
+ private static final short SHORT_UUID = 0x1238;
+
+ /**
+ * Gets the new 128-bit UUID of this characteristic.
+ *
+ * <p>Note: For GATT server only. GATT client should use {@link
+ * BeaconActionsCharacteristic#getId(BluetoothGattConnection)}.
+ */
+ public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
+
+ /**
+ * Gets the {@link UUID} of this characteristic.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID
+ * therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of
+ * the Fast Pair provider.
+ */
+ public static UUID getId(BluetoothGattConnection gattConnection) {
+ return getSupportedUuid(gattConnection, SHORT_UUID);
+ }
+
+ /**
+ * Enumerates all types of beacon actions.
+ */
+ // TODO(b/201673262): Convert enum to byte.
+ public enum BeaconActionType {
+ /**
+ * The value indicating that the type is for personalized name.
+ */
+ READ_BEACON_PARAMETERS((byte) 0x00),
+ READ_PROVISIONING_STATE((byte) 0x01),
+ SET_EPHEMERAL_IDENTITY_KEY((byte) 0x02),
+ CLEAR_EPHEMERAL_IDENTITY_KEY((byte) 0x03),
+ READ_EPHEMERAL_IDENTITY_KEY((byte) 0x04),
+ RING((byte) 0x05),
+ READ_RINGING_STATE((byte) 0x06),
+ UNKNOWN((byte) 0xFF); // and all others.
+
+ private final byte mValue;
+
+ BeaconActionType(byte value) {
+ this.mValue = value;
+ }
+
+ public byte getValue() {
+ return mValue;
+ }
+
+ /** Converts value to enum. */
+ public static BeaconActionType valueOf(byte value) {
+ for (BeaconActionType type : BeaconActionType.values()) {
+ if (type.getValue() == value) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+ }
+
+ private BeaconActionsCharacteristic() {
+ }
+ }
+
+ /**
+ * Characteristic to read for checking firmware version. 0X2A26 is assigned number from
+ * bluetooth SIG website.
+ */
+ public static final class FirmwareVersionCharacteristic {
+
+ /** UUID for firmware version. */
+ public static final UUID ID = to128BitUuid((short) 0x2A26);
+
+ private FirmwareVersionCharacteristic() {
+ }
+ }
+
+ private FastPairService() {
+ }
+ }
+
+ /**
+ * Defined by the BR/EDR Handover Profile. Pre-release version here:
+ * {https://jfarfel.users.x20web.corp.google.com/Bluetooth%20Handover%20d09.pdf}
+ */
+ public interface TransportDiscoveryService {
+
+ UUID ID = to128BitUuid((short) 0x1824);
+
+ byte BLUETOOTH_SIG_ORGANIZATION_ID = 0x01;
+ byte SERVICE_UUIDS_16_BIT_LIST_TYPE = 0x01;
+ byte SERVICE_UUIDS_32_BIT_LIST_TYPE = 0x02;
+ byte SERVICE_UUIDS_128_BIT_LIST_TYPE = 0x03;
+
+ /**
+ * Writing to this allows you to activate the BR/EDR transport.
+ */
+ interface ControlPointCharacteristic {
+
+ UUID ID = to128BitUuid((short) 0x2ABC);
+ byte ACTIVATE_TRANSPORT_OP_CODE = 0x01;
+ }
+
+ /**
+ * Info necessary to pair (mostly the Bluetooth Address).
+ */
+ interface BrHandoverDataCharacteristic {
+
+ UUID ID = to128BitUuid((short) 0x2C01);
+
+ /**
+ * All bits are reserved for future use.
+ */
+ byte BR_EDR_FEATURES = 0x00;
+ }
+
+ /**
+ * This characteristic exists only to wrap the descriptor.
+ */
+ interface BluetoothSigDataCharacteristic {
+
+ UUID ID = to128BitUuid((short) 0x2C02);
+
+ /**
+ * The entire Transport Block data (e.g. supported Bluetooth services).
+ */
+ interface BrTransportBlockDataDescriptor {
+
+ UUID ID = to128BitUuid((short) 0x2C03);
+ }
+ }
+ }
+
+ public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID =
+ to128BitUuid((short) 0x2902);
+
+ /**
+ * Wrapper for Bluetooth profile
+ */
+ public static class Profile {
+
+ public final int type;
+ public final String name;
+ public final String connectionStateAction;
+
+ private Profile(int type, String name, String connectionStateAction) {
+ this.type = type;
+ this.name = name;
+ this.connectionStateAction = connectionStateAction;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ /**
+ * {@link BluetoothHeadset} is used for both Headset and HandsFree (HFP).
+ */
+ private static final Profile HEADSET_AND_HANDS_FREE_PROFILE =
+ new Profile(
+ HEADSET, "HEADSET_AND_HANDS_FREE",
+ BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+
+ /** Fast Pair supported profiles. */
+ public static final ImmutableMap<Short, Profile> PROFILES =
+ ImmutableMap.<Short, Profile>builder()
+ .put(
+ Constants.A2DP_SINK_SERVICE_UUID,
+ new Profile(A2DP, "A2DP",
+ BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
+ .put(Constants.HEADSET_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE)
+ .put(Constants.HANDS_FREE_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE)
+ .build();
+
+ static short[] getSupportedProfiles() {
+ return Shorts.toArray(PROFILES.keySet());
+ }
+
+ /**
+ * Helper method of getting 128-bit UUID for Fast Pair custom GATT characteristics.
+ *
+ * <p>This method is designed for being backward compatible with old version of UUID therefore
+ * needs the {@link BluetoothGattConnection} parameter to check the supported status of the Fast
+ * Pair provider.
+ *
+ * <p>Note: For new custom GATT characteristics, don't need to use this helper and please just
+ * call {@code toFastPair128BitUuid(shortUuid)} to get the UUID. Which also implies that callers
+ * don't need to provide {@link BluetoothGattConnection} to get the UUID anymore.
+ */
+ private static UUID getSupportedUuid(BluetoothGattConnection gattConnection, short shortUuid) {
+ // In worst case (new characteristic not found), this method's performance impact is about
+ // 6ms
+ // by using Pixel2 + JBL LIVE220. And the impact should be less and less along with more and
+ // more devices adopt the new characteristics.
+ try {
+ // Checks the new UUID first.
+ if (gattConnection
+ .getCharacteristic(FastPairService.ID, toFastPair128BitUuid(shortUuid))
+ != null) {
+ Log.d(TAG, "Uses new KeyBasedPairingCharacteristic.ID");
+ return toFastPair128BitUuid(shortUuid);
+ }
+ } catch (BluetoothException e) {
+ Log.d(TAG, "Uses old KeyBasedPairingCharacteristic.ID");
+ }
+ // Returns the old UUID for default.
+ return to128BitUuid(shortUuid);
+ }
+
+ private Constants() {
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java
new file mode 100644
index 0000000..5bcf10a
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Like {@link SimpleBroadcastReceiver}, but for intents about a certain {@link BluetoothDevice}.
+ */
+abstract class DeviceIntentReceiver extends SimpleBroadcastReceiver {
+
+ private static final String TAG = DeviceIntentReceiver.class.getSimpleName();
+
+ private final BluetoothDevice mDevice;
+
+ static DeviceIntentReceiver oneShotReceiver(
+ Context context, Preferences preferences, BluetoothDevice device, String... actions) {
+ return new DeviceIntentReceiver(context, preferences, device, actions) {
+ @Override
+ protected void onReceiveDeviceIntent(Intent intent) throws Exception {
+ close();
+ }
+ };
+ }
+
+ /**
+ * @param context The context to use to register / unregister the receiver.
+ * @param device The interesting device. We ignore intents about other devices.
+ * @param actions The actions to include in our intent filter.
+ */
+ protected DeviceIntentReceiver(
+ Context context, Preferences preferences, BluetoothDevice device, String... actions) {
+ super(context, preferences, actions);
+ this.mDevice = device;
+ }
+
+ /**
+ * Called with intents about the interesting device (see {@link #DeviceIntentReceiver}). Any
+ * exception thrown by this method will be delivered via {@link #await}.
+ */
+ protected abstract void onReceiveDeviceIntent(Intent intent) throws Exception;
+
+ // incompatible types in argument.
+ @Override
+ protected void onReceive(Intent intent) throws Exception {
+ BluetoothDevice intentDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (mDevice == null || mDevice.equals(intentDevice)) {
+ onReceiveDeviceIntent(intent);
+ } else {
+ Log.v(TAG,
+ "Ignoring intent for device=" + maskBluetoothAddress(intentDevice)
+ + "(expected "
+ + maskBluetoothAddress(mDevice) + ")");
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
new file mode 100644
index 0000000..034c640
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
@@ -0,0 +1,779 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
+
+import androidx.annotation.Nullable;
+
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Shorts;
+
+import java.nio.ByteOrder;
+
+/**
+ * Preferences that tweak the Fast Pairing process: timeouts, number of retries... All preferences
+ * have default values which should be reasonable for all clients.
+ */
+@AutoValue
+public abstract class Preferences {
+
+ /**
+ * Timeout for each GATT operation (not for the whole pairing process).
+ */
+ public abstract int getGattOperationTimeoutSeconds();
+
+ /** Timeout for Gatt connection operation. */
+ public abstract int getGattConnectionTimeoutSeconds();
+
+ /** Timeout for Bluetooth toggle. */
+ public abstract int getBluetoothToggleTimeoutSeconds();
+
+ /** Sleep time for Bluetooth toggle. */
+ public abstract int getBluetoothToggleSleepSeconds();
+
+ /** Timeout for classic discovery. */
+ public abstract int getClassicDiscoveryTimeoutSeconds();
+
+ /** Number of discovery attempts allowed. */
+ public abstract int getNumDiscoverAttempts();
+
+ /** Sleep time between discovery retry. */
+ public abstract int getDiscoveryRetrySleepSeconds();
+
+ /** Whether to ignore error incurred during discovery. */
+ public abstract boolean getIgnoreDiscoveryError();
+
+ /** Timeout for Sdp. */
+ public abstract int getSdpTimeoutSeconds();
+
+ /** Number of Sdp attempts allowed. */
+ public abstract int getNumSdpAttempts();
+
+ /** Number of create bond attempts allowed. */
+ public abstract int getNumCreateBondAttempts();
+
+ /** Number of connect attempts allowed. */
+ public abstract int getNumConnectAttempts();
+
+ /** Number of write account key attempts allowed. */
+ public abstract int getNumWriteAccountKeyAttempts();
+
+ /** Returns whether it is OK toggle bluetooth to retry upon failure. */
+ public abstract boolean getToggleBluetoothOnFailure();
+
+ /** Whether to get Bluetooth state using polling. */
+ public abstract boolean getBluetoothStateUsesPolling();
+
+ /** Polling time when retrieving Bluetooth state. */
+ public abstract int getBluetoothStatePollingMillis();
+
+ /**
+ * The number of times to attempt a generic operation, before giving up.
+ */
+ public abstract int getNumAttempts();
+
+ /** Returns whether BrEdr handover is enabled. */
+ public abstract boolean getEnableBrEdrHandover();
+
+ /** Returns characteristic Id for Br Handover data. */
+ public abstract short getBrHandoverDataCharacteristicId();
+
+ /** Returns characteristic Id for Bluethoth Sig data. */
+ public abstract short getBluetoothSigDataCharacteristicId();
+
+ /** Returns characteristic Id for Firmware version. */
+ public abstract short getFirmwareVersionCharacteristicId();
+
+ /** Returns descripter Id for Br transport block data. */
+ public abstract short getBrTransportBlockDataDescriptorId();
+
+ /** Whether to wait for Uuids after bonding. */
+ public abstract boolean getWaitForUuidsAfterBonding();
+
+ /** Whether to get received Uuids and bonded events before close. */
+ public abstract boolean getReceiveUuidsAndBondedEventBeforeClose();
+
+ /** Timeout for remove bond operation. */
+ public abstract int getRemoveBondTimeoutSeconds();
+
+ /** Sleep time for remove bond operation. */
+ public abstract int getRemoveBondSleepMillis();
+
+ /**
+ * This almost always succeeds (or fails) in 2-10 seconds (Taimen running O -> Nexus 6P sim).
+ */
+ public abstract int getCreateBondTimeoutSeconds();
+
+ /** Timeout for creating bond with Hid devices. */
+ public abstract int getHidCreateBondTimeoutSeconds();
+
+ /** Timeout for get proxy operation. */
+ public abstract int getProxyTimeoutSeconds();
+
+ /** Whether to reject phone book access. */
+ public abstract boolean getRejectPhonebookAccess();
+
+ /** Whether to reject message access. */
+ public abstract boolean getRejectMessageAccess();
+
+ /** Whether to reject sim access. */
+ public abstract boolean getRejectSimAccess();
+
+ /** Sleep time for write account key operation. */
+ public abstract int getWriteAccountKeySleepMillis();
+
+ /** Whether to skip disconneting gatt before writing account key. */
+ public abstract boolean getSkipDisconnectingGattBeforeWritingAccountKey();
+
+ /** Whether to get more event log for quality improvement. */
+ public abstract boolean getMoreEventLogForQuality();
+
+ /** Whether to retry gatt connection and secrete handshake. */
+ public abstract boolean getRetryGattConnectionAndSecretHandshake();
+
+ /** Short Gatt connection timeoout. */
+ public abstract long getGattConnectShortTimeoutMs();
+
+ /** Long Gatt connection timeout. */
+ public abstract long getGattConnectLongTimeoutMs();
+
+ /** Short Timeout for Gatt connection, including retry. */
+ public abstract long getGattConnectShortTimeoutRetryMaxSpentTimeMs();
+
+ /** Timeout for address rotation, including retry. */
+ public abstract long getAddressRotateRetryMaxSpentTimeMs();
+
+ /** Returns pairing retry delay time. */
+ public abstract long getPairingRetryDelayMs();
+
+ /** Short timeout for secrete handshake. */
+ public abstract long getSecretHandshakeShortTimeoutMs();
+
+ /** Long timeout for secret handshake. */
+ public abstract long getSecretHandshakeLongTimeoutMs();
+
+ /** Short timeout for secret handshake, including retry. */
+ public abstract long getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs();
+
+ /** Long timeout for secret handshake, including retry. */
+ public abstract long getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs();
+
+ /** Number of secrete handshake retry allowed. */
+ public abstract long getSecretHandshakeRetryAttempts();
+
+ /** Timeout for secrete handshake and gatt connection, including retry. */
+ public abstract long getSecretHandshakeRetryGattConnectionMaxSpentTimeMs();
+
+ /** Timeout for signal lost handling, including retry. */
+ public abstract long getSignalLostRetryMaxSpentTimeMs();
+
+ /** Returns error for gatt connection and secrete handshake, without retry. */
+ public abstract ImmutableSet<Integer> getGattConnectionAndSecretHandshakeNoRetryGattError();
+
+ /** Whether to retry upon secrete handshake timeout. */
+ public abstract boolean getRetrySecretHandshakeTimeout();
+
+ /** Wehther to log user manual retry. */
+ public abstract boolean getLogUserManualRetry();
+
+ /** Returns number of pairing failure counts. */
+ public abstract int getPairFailureCounts();
+
+ /** Returns cached device address. */
+ public abstract String getCachedDeviceAddress();
+
+ /** Returns possible cached device address. */
+ public abstract String getPossibleCachedDeviceAddress();
+
+ /** Returns count of paired devices from the same model Id. */
+ public abstract int getSameModelIdPairedDeviceCount();
+
+ /** Whether the bonded device address is in the Cache . */
+ public abstract boolean getIsDeviceFinishCheckAddressFromCache();
+
+ /** Whether to log pairing info when cached model Id is hit. */
+ public abstract boolean getLogPairWithCachedModelId();
+
+ /** Whether to directly connnect to a profile of a device, whose model Id is in cache. */
+ public abstract boolean getDirectConnectProfileIfModelIdInCache();
+
+ /**
+ * Whether to auto-accept
+ * {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}.
+ * Only the Fast Pair Simulator (which runs on an Android device) sends this. Since real
+ * Bluetooth headphones don't have displays, they use secure simple pairing (no pin code
+ * confirmation; we get no pairing request broadcast at all). So we may want to turn this off in
+ * prod.
+ */
+ public abstract boolean getAcceptPasskey();
+
+ /** Returns Uuids for supported profiles. */
+ @SuppressWarnings("mutable")
+ public abstract byte[] getSupportedProfileUuids();
+
+ /**
+ * If true, after the Key-based Pairing BLE handshake, we wait for the headphones to send a
+ * pairing request to us; if false, we send the request to them.
+ */
+ public abstract boolean getProviderInitiatesBondingIfSupported();
+
+ /**
+ * If true, the first step will be attempting to connect directly to our supported profiles when
+ * a device has previously been bonded. This will help with performance on subsequent bondings
+ * and help to increase reliability in some cases.
+ */
+ public abstract boolean getAttemptDirectConnectionWhenPreviouslyBonded();
+
+ /**
+ * If true, closed Gatt connections will be reopened when they are needed again. Otherwise, they
+ * will remain closed until they are explicitly reopened.
+ */
+ public abstract boolean getAutomaticallyReconnectGattWhenNeeded();
+
+ /**
+ * If true, we'll finish the pairing process after we've created a bond instead of after
+ * connecting a profile.
+ */
+ public abstract boolean getSkipConnectingProfiles();
+
+ /**
+ * If true, continues the pairing process if we've timed out due to not receiving UUIDs from the
+ * headset. We can still attempt to connect to A2DP afterwards. If false, Fast Pair will fail
+ * after this step since we're expecting to receive the UUIDs.
+ */
+ public abstract boolean getIgnoreUuidTimeoutAfterBonded();
+
+ /**
+ * If true, a specific transport type will be included in the create bond request, which will be
+ * used for dual mode devices. Otherwise, we'll use the platform defined default which is
+ * BluetoothDevice.TRANSPORT_AUTO. See {@link #getCreateBondTransportType()}.
+ */
+ public abstract boolean getSpecifyCreateBondTransportType();
+
+ /**
+ * The transport type to use when creating a bond when
+ * {@link #getSpecifyCreateBondTransportType()} is true. This should be one of
+ * BluetoothDevice.TRANSPORT_AUTO, BluetoothDevice.TRANSPORT_BREDR, or
+ * BluetoothDevice.TRANSPORT_LE.
+ */
+ public abstract int getCreateBondTransportType();
+
+ /** Whether to increase intent filter priority. */
+ public abstract boolean getIncreaseIntentFilterPriority();
+
+ /** Whether to evaluate performance. */
+ public abstract boolean getEvaluatePerformance();
+
+ /** Returns extra logging information. */
+ @Nullable
+ public abstract ExtraLoggingInformation getExtraLoggingInformation();
+
+ /** Whether to enable naming characteristic. */
+ public abstract boolean getEnableNamingCharacteristic();
+
+ /** Whether to enable firmware version characteristic. */
+ public abstract boolean getEnableFirmwareVersionCharacteristic();
+
+ /**
+ * If true, even Fast Pair identifies a provider have paired with the account, still writes the
+ * identified account key to the provider.
+ */
+ public abstract boolean getKeepSameAccountKeyWrite();
+
+ /**
+ * If true, run retroactive pairing.
+ */
+ public abstract boolean getIsRetroactivePairing();
+
+ /**
+ * If it's larger than 0, {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp} would be
+ * triggered with number of attempts after device is bonded and no profiles were automatically
+ * discovered".
+ */
+ public abstract int getNumSdpAttemptsAfterBonded();
+
+ /**
+ * If true, supports HID device for fastpair.
+ */
+ public abstract boolean getSupportHidDevice();
+
+ /**
+ * If true, we'll enable the pairing behavior to handle the state transition from BOND_BONDED to
+ * BOND_BONDING when directly connecting profiles.
+ */
+ public abstract boolean getEnablePairingWhileDirectlyConnecting();
+
+ /**
+ * If true, we will accept the user confirmation when bonding with FastPair 1.0 devices.
+ */
+ public abstract boolean getAcceptConsentForFastPairOne();
+
+ /**
+ * If it's larger than 0, we will retry connecting GATT within the timeout.
+ */
+ public abstract int getGattConnectRetryTimeoutMillis();
+
+ /**
+ * If true, then uses the new custom GATT characteristics {go/fastpair-128bit-gatt}.
+ */
+ public abstract boolean getEnable128BitCustomGattCharacteristicsId();
+
+ /**
+ * If true, then sends the internal pair step or Exception to Validator by Intent.
+ */
+ public abstract boolean getEnableSendExceptionStepToValidator();
+
+ /**
+ * If true, then adds the additional data type in the handshake packet when action over BLE.
+ */
+ public abstract boolean getEnableAdditionalDataTypeWhenActionOverBle();
+
+ /**
+ * If true, then checks the bond state when skips connecting profiles in the pairing shortcut.
+ */
+ public abstract boolean getCheckBondStateWhenSkipConnectingProfiles();
+
+ /**
+ * If true, the passkey confirmation will be handled by the half-sheet UI.
+ */
+ public abstract boolean getHandlePasskeyConfirmationByUi();
+
+ /**
+ * If true, then use pair flow to show ui when pairing is finished without connecting profile.
+ */
+ public abstract boolean getEnablePairFlowShowUiWithoutProfileConnection();
+
+ /** Converts an instance to a builder. */
+ public abstract Builder toBuilder();
+
+ /** Constructs a builder. */
+ public static Builder builder() {
+ return new AutoValue_Preferences.Builder()
+ .setGattOperationTimeoutSeconds(3)
+ .setGattConnectionTimeoutSeconds(15)
+ .setBluetoothToggleTimeoutSeconds(10)
+ .setBluetoothToggleSleepSeconds(2)
+ .setClassicDiscoveryTimeoutSeconds(10)
+ .setNumDiscoverAttempts(3)
+ .setDiscoveryRetrySleepSeconds(1)
+ .setIgnoreDiscoveryError(false)
+ .setSdpTimeoutSeconds(10)
+ .setNumSdpAttempts(3)
+ .setNumCreateBondAttempts(3)
+ .setNumConnectAttempts(1)
+ .setNumWriteAccountKeyAttempts(3)
+ .setToggleBluetoothOnFailure(false)
+ .setBluetoothStateUsesPolling(true)
+ .setBluetoothStatePollingMillis(1000)
+ .setNumAttempts(2)
+ .setEnableBrEdrHandover(false)
+ .setBrHandoverDataCharacteristicId(get16BitUuid(
+ Constants.TransportDiscoveryService.BrHandoverDataCharacteristic.ID))
+ .setBluetoothSigDataCharacteristicId(get16BitUuid(
+ Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic.ID))
+ .setFirmwareVersionCharacteristicId(get16BitUuid(FirmwareVersionCharacteristic.ID))
+ .setBrTransportBlockDataDescriptorId(
+ get16BitUuid(
+ Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic
+ .BrTransportBlockDataDescriptor.ID))
+ .setWaitForUuidsAfterBonding(true)
+ .setReceiveUuidsAndBondedEventBeforeClose(true)
+ .setRemoveBondTimeoutSeconds(5)
+ .setRemoveBondSleepMillis(1000)
+ .setCreateBondTimeoutSeconds(15)
+ .setHidCreateBondTimeoutSeconds(40)
+ .setProxyTimeoutSeconds(2)
+ .setRejectPhonebookAccess(false)
+ .setRejectMessageAccess(false)
+ .setRejectSimAccess(false)
+ .setAcceptPasskey(true)
+ .setSupportedProfileUuids(Constants.getSupportedProfiles())
+ .setWriteAccountKeySleepMillis(2000)
+ .setProviderInitiatesBondingIfSupported(false)
+ .setAttemptDirectConnectionWhenPreviouslyBonded(false)
+ .setAutomaticallyReconnectGattWhenNeeded(false)
+ .setSkipDisconnectingGattBeforeWritingAccountKey(false)
+ .setSkipConnectingProfiles(false)
+ .setIgnoreUuidTimeoutAfterBonded(false)
+ .setSpecifyCreateBondTransportType(false)
+ .setCreateBondTransportType(0 /*BluetoothDevice.TRANSPORT_AUTO*/)
+ .setIncreaseIntentFilterPriority(true)
+ .setEvaluatePerformance(false)
+ .setKeepSameAccountKeyWrite(true)
+ .setEnableNamingCharacteristic(false)
+ .setEnableFirmwareVersionCharacteristic(false)
+ .setIsRetroactivePairing(false)
+ .setNumSdpAttemptsAfterBonded(1)
+ .setSupportHidDevice(false)
+ .setEnablePairingWhileDirectlyConnecting(true)
+ .setAcceptConsentForFastPairOne(true)
+ .setGattConnectRetryTimeoutMillis(0)
+ .setEnable128BitCustomGattCharacteristicsId(true)
+ .setEnableSendExceptionStepToValidator(true)
+ .setEnableAdditionalDataTypeWhenActionOverBle(true)
+ .setCheckBondStateWhenSkipConnectingProfiles(true)
+ .setHandlePasskeyConfirmationByUi(false)
+ .setMoreEventLogForQuality(true)
+ .setRetryGattConnectionAndSecretHandshake(true)
+ .setGattConnectShortTimeoutMs(7000)
+ .setGattConnectLongTimeoutMs(15000)
+ .setGattConnectShortTimeoutRetryMaxSpentTimeMs(10000)
+ .setAddressRotateRetryMaxSpentTimeMs(15000)
+ .setPairingRetryDelayMs(100)
+ .setSecretHandshakeShortTimeoutMs(3000)
+ .setSecretHandshakeLongTimeoutMs(10000)
+ .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(5000)
+ .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(7000)
+ .setSecretHandshakeRetryAttempts(3)
+ .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(15000)
+ .setSignalLostRetryMaxSpentTimeMs(15000)
+ .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of())
+ .setRetrySecretHandshakeTimeout(false)
+ .setLogUserManualRetry(true)
+ .setPairFailureCounts(0)
+ .setEnablePairFlowShowUiWithoutProfileConnection(true)
+ .setPairFailureCounts(0)
+ .setLogPairWithCachedModelId(true)
+ .setDirectConnectProfileIfModelIdInCache(false)
+ .setCachedDeviceAddress("")
+ .setPossibleCachedDeviceAddress("")
+ .setSameModelIdPairedDeviceCount(0)
+ .setIsDeviceFinishCheckAddressFromCache(true);
+ }
+
+ /**
+ * Preferences builder.
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ /** Set gatt operation timeout. */
+ public abstract Builder setGattOperationTimeoutSeconds(int value);
+
+ /** Set gatt connection timeout. */
+ public abstract Builder setGattConnectionTimeoutSeconds(int value);
+
+ /** Set bluetooth toggle timeout. */
+ public abstract Builder setBluetoothToggleTimeoutSeconds(int value);
+
+ /** Set bluetooth toggle sleep time. */
+ public abstract Builder setBluetoothToggleSleepSeconds(int value);
+
+ /** Set classic discovery timeout. */
+ public abstract Builder setClassicDiscoveryTimeoutSeconds(int value);
+
+ /** Set number of discover attempts allowed. */
+ public abstract Builder setNumDiscoverAttempts(int value);
+
+ /** Set discovery retry sleep time. */
+ public abstract Builder setDiscoveryRetrySleepSeconds(int value);
+
+ /** Set whether to ignore discovery error. */
+ public abstract Builder setIgnoreDiscoveryError(boolean value);
+
+ /** Set sdp timeout. */
+ public abstract Builder setSdpTimeoutSeconds(int value);
+
+ /** Set number of sdp attempts allowed. */
+ public abstract Builder setNumSdpAttempts(int value);
+
+ /** Set number of allowed attempts to create bond. */
+ public abstract Builder setNumCreateBondAttempts(int value);
+
+ /** Set number of connect attempts allowed. */
+ public abstract Builder setNumConnectAttempts(int value);
+
+ /** Set number of write account key attempts allowed. */
+ public abstract Builder setNumWriteAccountKeyAttempts(int value);
+
+ /** Set whether to retry by bluetooth toggle on failure. */
+ public abstract Builder setToggleBluetoothOnFailure(boolean value);
+
+ /** Set whether to use polling to set bluetooth status. */
+ public abstract Builder setBluetoothStateUsesPolling(boolean value);
+
+ /** Set Bluetooth state polling timeout. */
+ public abstract Builder setBluetoothStatePollingMillis(int value);
+
+ /** Set number of attempts. */
+ public abstract Builder setNumAttempts(int value);
+
+ /** Set whether to enable BrEdr handover. */
+ public abstract Builder setEnableBrEdrHandover(boolean value);
+
+ /** Set Br handover data characteristic Id. */
+ public abstract Builder setBrHandoverDataCharacteristicId(short value);
+
+ /** Set Bluetooth Sig data characteristic Id. */
+ public abstract Builder setBluetoothSigDataCharacteristicId(short value);
+
+ /** Set Firmware version characteristic id. */
+ public abstract Builder setFirmwareVersionCharacteristicId(short value);
+
+ /** Set Br transport block data descriptor id. */
+ public abstract Builder setBrTransportBlockDataDescriptorId(short value);
+
+ /** Set whether to wait for Uuids after bonding. */
+ public abstract Builder setWaitForUuidsAfterBonding(boolean value);
+
+ /** Set whether to receive Uuids and bonded event before close. */
+ public abstract Builder setReceiveUuidsAndBondedEventBeforeClose(boolean value);
+
+ /** Set remove bond timeout. */
+ public abstract Builder setRemoveBondTimeoutSeconds(int value);
+
+ /** Set remove bound sleep time. */
+ public abstract Builder setRemoveBondSleepMillis(int value);
+
+ /** Set create bond timeout. */
+ public abstract Builder setCreateBondTimeoutSeconds(int value);
+
+ /** Set Hid create bond timeout. */
+ public abstract Builder setHidCreateBondTimeoutSeconds(int value);
+
+ /** Set proxy timeout. */
+ public abstract Builder setProxyTimeoutSeconds(int value);
+
+ /** Set whether to reject phone book access. */
+ public abstract Builder setRejectPhonebookAccess(boolean value);
+
+ /** Set whether to reject message access. */
+ public abstract Builder setRejectMessageAccess(boolean value);
+
+ /** Set whether to reject slim access. */
+ public abstract Builder setRejectSimAccess(boolean value);
+
+ /** Set whether to accept passkey. */
+ public abstract Builder setAcceptPasskey(boolean value);
+
+ /** Set supported profile Uuids. */
+ public abstract Builder setSupportedProfileUuids(byte[] value);
+
+ /** Set whether to collect more event log for quality. */
+ public abstract Builder setMoreEventLogForQuality(boolean value);
+
+ /** Set supported profile Uuids. */
+ public Builder setSupportedProfileUuids(short... uuids) {
+ return setSupportedProfileUuids(Bytes.toBytes(ByteOrder.BIG_ENDIAN, uuids));
+ }
+
+ /** Set write account key sleep time. */
+ public abstract Builder setWriteAccountKeySleepMillis(int value);
+
+ /** Set whether to do provider initialized bonding if supported. */
+ public abstract Builder setProviderInitiatesBondingIfSupported(boolean value);
+
+ /** Set whether to try direct connection when the device is previously bonded. */
+ public abstract Builder setAttemptDirectConnectionWhenPreviouslyBonded(boolean value);
+
+ /** Set whether to automatically reconnect gatt when needed. */
+ public abstract Builder setAutomaticallyReconnectGattWhenNeeded(boolean value);
+
+ /** Set whether to skip disconnecting gatt before writing account key. */
+ public abstract Builder setSkipDisconnectingGattBeforeWritingAccountKey(boolean value);
+
+ /** Set whether to skip connecting profiles. */
+ public abstract Builder setSkipConnectingProfiles(boolean value);
+
+ /** Set whether to ignore Uuid timeout after bonded. */
+ public abstract Builder setIgnoreUuidTimeoutAfterBonded(boolean value);
+
+ /** Set whether to include transport type in create bound request. */
+ public abstract Builder setSpecifyCreateBondTransportType(boolean value);
+
+ /** Set transport type used in create bond request. */
+ public abstract Builder setCreateBondTransportType(int value);
+
+ /** Set whether to increase intent filter priority. */
+ public abstract Builder setIncreaseIntentFilterPriority(boolean value);
+
+ /** Set whether to evaluate performance. */
+ public abstract Builder setEvaluatePerformance(boolean value);
+
+ /** Set extra logging info. */
+ public abstract Builder setExtraLoggingInformation(ExtraLoggingInformation value);
+
+ /** Set whether to enable naming characteristic. */
+ public abstract Builder setEnableNamingCharacteristic(boolean value);
+
+ /**
+ * Set whether to keep writing the account key to the provider,
+ * that has already paired with the account.
+ */
+ public abstract Builder setKeepSameAccountKeyWrite(boolean value);
+
+ /** Set whether to enable firmware version characteristic. */
+ public abstract Builder setEnableFirmwareVersionCharacteristic(boolean value);
+
+ /** Set whether it is retroactive pairing. */
+ public abstract Builder setIsRetroactivePairing(boolean value);
+
+ /** Set number of allowed sdp attempts after bonded. */
+ public abstract Builder setNumSdpAttemptsAfterBonded(int value);
+
+ /** Set whether to support Hid device. */
+ public abstract Builder setSupportHidDevice(boolean value);
+
+ /**
+ * Set wehther to enable the pairing behavior to handle the state transition from
+ * BOND_BONDED to BOND_BONDING when directly connecting profiles.
+ */
+ public abstract Builder setEnablePairingWhileDirectlyConnecting(boolean value);
+
+ /** Set whether to accept consent for fast pair one. */
+ public abstract Builder setAcceptConsentForFastPairOne(boolean value);
+
+ /** Set Gatt connect retry timeout. */
+ public abstract Builder setGattConnectRetryTimeoutMillis(int value);
+
+ /** Set whether to enable 128 bit custom gatt characteristic Id. */
+ public abstract Builder setEnable128BitCustomGattCharacteristicsId(boolean value);
+
+ /** Set whether to send exception step to validator. */
+ public abstract Builder setEnableSendExceptionStepToValidator(boolean value);
+
+ /** Set wehther to add the additional data type in the handshake when action over BLE. */
+ public abstract Builder setEnableAdditionalDataTypeWhenActionOverBle(boolean value);
+
+ /** Set whether to check bond state when skip connecting profiles. */
+ public abstract Builder setCheckBondStateWhenSkipConnectingProfiles(boolean value);
+
+ /** Set whether to handle passkey confirmation by UI. */
+ public abstract Builder setHandlePasskeyConfirmationByUi(boolean value);
+
+ /** Set wehther to retry gatt connection and secret handshake. */
+ public abstract Builder setRetryGattConnectionAndSecretHandshake(boolean value);
+
+ /** Set gatt connect short timeout. */
+ public abstract Builder setGattConnectShortTimeoutMs(long value);
+
+ /** Set gatt connect long timeout. */
+ public abstract Builder setGattConnectLongTimeoutMs(long value);
+
+ /** Set gatt connection short timoutout, including retry. */
+ public abstract Builder setGattConnectShortTimeoutRetryMaxSpentTimeMs(long value);
+
+ /** Set address rotate timeout, including retry. */
+ public abstract Builder setAddressRotateRetryMaxSpentTimeMs(long value);
+
+ /** Set pairing retry delay time. */
+ public abstract Builder setPairingRetryDelayMs(long value);
+
+ /** Set secret handshake short timeout. */
+ public abstract Builder setSecretHandshakeShortTimeoutMs(long value);
+
+ /** Set secret handshake long timeout. */
+ public abstract Builder setSecretHandshakeLongTimeoutMs(long value);
+
+ /** Set secret handshake short timeout retry max spent time. */
+ public abstract Builder setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(long value);
+
+ /** Set secret handshake long timeout retry max spent time. */
+ public abstract Builder setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(long value);
+
+ /** Set secret handshake retry attempts allowed. */
+ public abstract Builder setSecretHandshakeRetryAttempts(long value);
+
+ /** Set secret handshake retry gatt connection max spent time. */
+ public abstract Builder setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(long value);
+
+ /** Set signal loss retry max spent time. */
+ public abstract Builder setSignalLostRetryMaxSpentTimeMs(long value);
+
+ /** Set gatt connection and secret handshake no retry gatt error. */
+ public abstract Builder setGattConnectionAndSecretHandshakeNoRetryGattError(
+ ImmutableSet<Integer> value);
+
+ /** Set retry secret handshake timeout. */
+ public abstract Builder setRetrySecretHandshakeTimeout(boolean value);
+
+ /** Set whether to log user manual retry. */
+ public abstract Builder setLogUserManualRetry(boolean value);
+
+ /** Set pair falure counts. */
+ public abstract Builder setPairFailureCounts(int counts);
+
+ /**
+ * Set whether to use pair flow to show ui when pairing is finished without connecting
+ * profile..
+ */
+ public abstract Builder setEnablePairFlowShowUiWithoutProfileConnection(boolean value);
+
+ /** Set whether to log pairing with cached module Id. */
+ public abstract Builder setLogPairWithCachedModelId(boolean value);
+
+ /** Set possible cached device address. */
+ public abstract Builder setPossibleCachedDeviceAddress(String value);
+
+ /** Set paired device count from the same module Id. */
+ public abstract Builder setSameModelIdPairedDeviceCount(int value);
+
+ /** Set whether the bonded device address is from cache. */
+ public abstract Builder setIsDeviceFinishCheckAddressFromCache(boolean value);
+
+ /** Set whether to directly connect profile if modelId is in cache. */
+ public abstract Builder setDirectConnectProfileIfModelIdInCache(boolean value);
+
+ /** Set cached device address. */
+ public abstract Builder setCachedDeviceAddress(String value);
+
+ /** Builds a Preferences instance. */
+ public abstract Preferences build();
+ }
+
+ /** Whether a given Uuid is supported. */
+ public boolean isSupportedProfile(short profileUuid) {
+ return Constants.PROFILES.containsKey(profileUuid)
+ && Shorts.contains(
+ Bytes.toShorts(ByteOrder.BIG_ENDIAN, getSupportedProfileUuids()), profileUuid);
+ }
+
+ /**
+ * Information that will be used for logging.
+ */
+ @AutoValue
+ public abstract static class ExtraLoggingInformation {
+
+ /** Returns model Id. */
+ public abstract String getModelId();
+
+ /** Converts an instance to a builder. */
+ public abstract Builder toBuilder();
+
+ /** Creates a builder for ExtraLoggingInformation. */
+ public static Builder builder() {
+ return new AutoValue_Preferences_ExtraLoggingInformation.Builder();
+ }
+
+ /**
+ * Extra logging information builder.
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ /** Set model ID. */
+ public abstract Builder setModelId(String modelId);
+
+ /** Builds extra logging information. */
+ public abstract ExtraLoggingInformation build();
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java
new file mode 100644
index 0000000..7f525a7
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Like {@link BroadcastReceiver}, but:
+ *
+ * <ul>
+ * <li>Simpler to create and register, with a list of actions.
+ * <li>Implements AutoCloseable. If used as a resource in try-with-resources (available on
+ * KitKat+), unregisters itself automatically.
+ * <li>Lets you block waiting for your state transition with {@link #await}.
+ * </ul>
+ */
+// AutoCloseable only available on KitKat+.
+@TargetApi(VERSION_CODES.KITKAT)
+public abstract class SimpleBroadcastReceiver extends BroadcastReceiver implements AutoCloseable {
+
+ private static final String TAG = SimpleBroadcastReceiver.class.getSimpleName();
+
+ /**
+ * Creates a one shot receiver.
+ */
+ public static SimpleBroadcastReceiver oneShotReceiver(
+ Context context, Preferences preferences, String... actions) {
+ return new SimpleBroadcastReceiver(context, preferences, actions) {
+ @Override
+ protected void onReceive(Intent intent) {
+ close();
+ }
+ };
+ }
+
+ private final Context mContext;
+ private final SettableFuture<Void> mIsClosedFuture = SettableFuture.create();
+ private long mAwaitExtendSecond;
+
+ // Nullness checker complains about 'this' being @UnderInitialization
+ @SuppressWarnings("nullness")
+ public SimpleBroadcastReceiver(
+ Context context, Preferences preferences, @Nullable Handler handler,
+ String... actions) {
+ Log.v(TAG, this + " listening for actions " + Arrays.toString(actions));
+ this.mContext = context;
+ IntentFilter intentFilter = new IntentFilter();
+ if (preferences.getIncreaseIntentFilterPriority()) {
+ intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ }
+ for (String action : actions) {
+ intentFilter.addAction(action);
+ }
+ context.registerReceiver(this, intentFilter, /* broadcastPermission= */ null, handler);
+ }
+
+ public SimpleBroadcastReceiver(Context context, Preferences preferences, String... actions) {
+ this(context, preferences, /* handler= */ null, actions);
+ }
+
+ /**
+ * Any exception thrown by this method will be delivered via {@link #await}.
+ */
+ protected abstract void onReceive(Intent intent) throws Exception;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "Got intent with action= " + intent.getAction());
+ try {
+ onReceive(intent);
+ } catch (Exception e) {
+ closeWithError(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ closeWithError(null);
+ }
+
+ void closeWithError(@Nullable Exception e) {
+ try {
+ mContext.unregisterReceiver(this);
+ } catch (IllegalArgumentException ignored) {
+ // Ignore. Happens if you unregister twice.
+ }
+ if (e == null) {
+ mIsClosedFuture.set(null);
+ } else {
+ mIsClosedFuture.setException(e);
+ }
+ }
+
+ /**
+ * Extends the awaiting time.
+ */
+ public void extendAwaitSecond(int awaitExtendSecond) {
+ this.mAwaitExtendSecond = awaitExtendSecond;
+ }
+
+ /**
+ * Blocks until this receiver has closed (i.e. the state transition that this receiver is
+ * interested in has completed). Throws an exception on any error.
+ */
+ public void await(long timeout, TimeUnit timeUnit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ Log.v(TAG, this + " waiting on future for " + timeout + " " + timeUnit);
+ try {
+ mIsClosedFuture.get(timeout, timeUnit);
+ } catch (TimeoutException e) {
+ if (mAwaitExtendSecond <= 0) {
+ throw e;
+ }
+ Log.i(TAG, "Extend timeout for " + mAwaitExtendSecond + " seconds");
+ mIsClosedFuture.get(mAwaitExtendSecond, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java b/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
new file mode 100644
index 0000000..5084e7e
--- /dev/null
+++ b/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Unit tests for {@link AccountKeyGenerator}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AccountKeyGeneratorTest {
+ @Test
+ public void createAccountKey() throws NoSuchAlgorithmException {
+ byte[] accountKey = AccountKeyGenerator.createAccountKey();
+
+ assertThat(accountKey).hasLength(16);
+ assertThat(accountKey[0]).isEqualTo(AccountKeyCharacteristic.TYPE);
+ }
+}
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
new file mode 100644
index 0000000..a0933a9
--- /dev/null
+++ b/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.toFastPair128BitUuid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
+import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link Constants}.
+ */
+public class ConstantsTest extends TestCase {
+
+ @Mock
+ private BluetoothGattConnection mMockGattConnection;
+
+ private static final UUID OLD_KEY_BASE_PAIRING_CHARACTERISTICS = to128BitUuid((short) 0x1234);
+
+ private static final UUID NEW_KEY_BASE_PAIRING_CHARACTERISTICS =
+ toFastPair128BitUuid((short) 0x1234);
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initMocks(this);
+ }
+
+ public void test_getId_whenSupportNewCharacteristics() throws BluetoothException {
+ when(mMockGattConnection.getCharacteristic(any(UUID.class), any(UUID.class)))
+ .thenReturn(new BluetoothGattCharacteristic(NEW_KEY_BASE_PAIRING_CHARACTERISTICS, 0,
+ 0));
+
+ assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
+ .isEqualTo(NEW_KEY_BASE_PAIRING_CHARACTERISTICS);
+ }
+
+ public void test_getId_whenNotSupportNewCharacteristics() throws BluetoothException {
+ // {@link BluetoothGattConnection#getCharacteristic(UUID, UUID)} throws {@link
+ // BluetoothException} if the characteristic not found .
+ when(mMockGattConnection.getCharacteristic(any(UUID.class), any(UUID.class)))
+ .thenThrow(new BluetoothException(""));
+
+ assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
+ .isEqualTo(OLD_KEY_BASE_PAIRING_CHARACTERISTICS);
+ }
+}