Merge "Update preparers to align across everything" am: e2c1c6fadd am: 2e5a397292 am: 4a5c7b567a

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2401533

Change-Id: I876b742faef5f0e18ca51471d5a9d73512568c72
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 87b0a64..6cbed46 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -51,6 +51,208 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.nearby.DataElement> CREATOR;
   }
 
+  public class FastPairAccountKeyDeviceMetadata {
+    method @Nullable public byte[] getDeviceAccountKey();
+    method @Nullable public android.nearby.FastPairDeviceMetadata getFastPairDeviceMetadata();
+    method @Nullable public android.nearby.FastPairDiscoveryItem getFastPairDiscoveryItem();
+    method @Nullable public byte[] getSha256DeviceAccountKeyPublicAddress();
+  }
+
+  public static final class FastPairAccountKeyDeviceMetadata.Builder {
+    ctor public FastPairAccountKeyDeviceMetadata.Builder();
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata build();
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata.Builder setDeviceAccountKey(@Nullable byte[]);
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata.Builder setFastPairDeviceMetadata(@Nullable android.nearby.FastPairDeviceMetadata);
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata.Builder setFastPairDiscoveryItem(@Nullable android.nearby.FastPairDiscoveryItem);
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata.Builder setSha256DeviceAccountKeyPublicAddress(@Nullable byte[]);
+  }
+
+  public class FastPairAntispoofKeyDeviceMetadata {
+    method @Nullable public byte[] getAntispoofPublicKey();
+    method @Nullable public android.nearby.FastPairDeviceMetadata getFastPairDeviceMetadata();
+  }
+
+  public static final class FastPairAntispoofKeyDeviceMetadata.Builder {
+    ctor public FastPairAntispoofKeyDeviceMetadata.Builder();
+    method @NonNull public android.nearby.FastPairAntispoofKeyDeviceMetadata build();
+    method @NonNull public android.nearby.FastPairAntispoofKeyDeviceMetadata.Builder setAntispoofPublicKey(@Nullable byte[]);
+    method @NonNull public android.nearby.FastPairAntispoofKeyDeviceMetadata.Builder setFastPairDeviceMetadata(@Nullable android.nearby.FastPairDeviceMetadata);
+  }
+
+  public abstract class FastPairDataProviderService extends android.app.Service {
+    ctor public FastPairDataProviderService(@NonNull String);
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onLoadFastPairAccountDevicesMetadata(@NonNull android.nearby.FastPairDataProviderService.FastPairAccountDevicesMetadataRequest, @NonNull android.nearby.FastPairDataProviderService.FastPairAccountDevicesMetadataCallback);
+    method public abstract void onLoadFastPairAntispoofKeyDeviceMetadata(@NonNull android.nearby.FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest, @NonNull android.nearby.FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback);
+    method public abstract void onLoadFastPairEligibleAccounts(@NonNull android.nearby.FastPairDataProviderService.FastPairEligibleAccountsRequest, @NonNull android.nearby.FastPairDataProviderService.FastPairEligibleAccountsCallback);
+    method public abstract void onManageFastPairAccount(@NonNull android.nearby.FastPairDataProviderService.FastPairManageAccountRequest, @NonNull android.nearby.FastPairDataProviderService.FastPairManageActionCallback);
+    method public abstract void onManageFastPairAccountDevice(@NonNull android.nearby.FastPairDataProviderService.FastPairManageAccountDeviceRequest, @NonNull android.nearby.FastPairDataProviderService.FastPairManageActionCallback);
+    field public static final String ACTION_FAST_PAIR_DATA_PROVIDER = "android.nearby.action.FAST_PAIR_DATA_PROVIDER";
+    field public static final int ERROR_CODE_BAD_REQUEST = 0; // 0x0
+    field public static final int ERROR_CODE_INTERNAL_ERROR = 1; // 0x1
+    field public static final int MANAGE_REQUEST_ADD = 0; // 0x0
+    field public static final int MANAGE_REQUEST_REMOVE = 1; // 0x1
+  }
+
+  public static interface FastPairDataProviderService.FastPairAccountDevicesMetadataCallback {
+    method public void onError(int, @Nullable String);
+    method public void onFastPairAccountDevicesMetadataReceived(@NonNull java.util.Collection<android.nearby.FastPairAccountKeyDeviceMetadata>);
+  }
+
+  public static class FastPairDataProviderService.FastPairAccountDevicesMetadataRequest {
+    method @NonNull public android.accounts.Account getAccount();
+    method @NonNull public java.util.Collection<byte[]> getDeviceAccountKeys();
+  }
+
+  public static interface FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback {
+    method public void onError(int, @Nullable String);
+    method public void onFastPairAntispoofKeyDeviceMetadataReceived(@NonNull android.nearby.FastPairAntispoofKeyDeviceMetadata);
+  }
+
+  public static class FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest {
+    method @NonNull public byte[] getModelId();
+  }
+
+  public static interface FastPairDataProviderService.FastPairEligibleAccountsCallback {
+    method public void onError(int, @Nullable String);
+    method public void onFastPairEligibleAccountsReceived(@NonNull java.util.Collection<android.nearby.FastPairEligibleAccount>);
+  }
+
+  public static class FastPairDataProviderService.FastPairEligibleAccountsRequest {
+  }
+
+  public static class FastPairDataProviderService.FastPairManageAccountDeviceRequest {
+    method @NonNull public android.accounts.Account getAccount();
+    method @NonNull public android.nearby.FastPairAccountKeyDeviceMetadata getAccountKeyDeviceMetadata();
+    method public int getRequestType();
+  }
+
+  public static class FastPairDataProviderService.FastPairManageAccountRequest {
+    method @NonNull public android.accounts.Account getAccount();
+    method public int getRequestType();
+  }
+
+  public static interface FastPairDataProviderService.FastPairManageActionCallback {
+    method public void onError(int, @Nullable String);
+    method public void onSuccess();
+  }
+
+  public class FastPairDeviceMetadata {
+    method public int getBleTxPower();
+    method @Nullable public String getConnectSuccessCompanionAppInstalled();
+    method @Nullable public String getConnectSuccessCompanionAppNotInstalled();
+    method public int getDeviceType();
+    method @Nullable public String getDownloadCompanionAppDescription();
+    method @Nullable public String getFailConnectGoToSettingsDescription();
+    method @Nullable public byte[] getImage();
+    method @Nullable public String getImageUrl();
+    method @Nullable public String getInitialNotificationDescription();
+    method @Nullable public String getInitialNotificationDescriptionNoAccount();
+    method @Nullable public String getInitialPairingDescription();
+    method @Nullable public String getIntentUri();
+    method @Nullable public String getName();
+    method @Nullable public String getOpenCompanionAppDescription();
+    method @Nullable public String getRetroactivePairingDescription();
+    method @Nullable public String getSubsequentPairingDescription();
+    method public float getTriggerDistance();
+    method @Nullable public String getTrueWirelessImageUrlCase();
+    method @Nullable public String getTrueWirelessImageUrlLeftBud();
+    method @Nullable public String getTrueWirelessImageUrlRightBud();
+    method @Nullable public String getUnableToConnectDescription();
+    method @Nullable public String getUnableToConnectTitle();
+    method @Nullable public String getUpdateCompanionAppDescription();
+    method @Nullable public String getWaitLaunchCompanionAppDescription();
+  }
+
+  public static final class FastPairDeviceMetadata.Builder {
+    ctor public FastPairDeviceMetadata.Builder();
+    method @NonNull public android.nearby.FastPairDeviceMetadata build();
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setBleTxPower(int);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setConnectSuccessCompanionAppInstalled(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setConnectSuccessCompanionAppNotInstalled(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setDeviceType(int);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setDownloadCompanionAppDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setFailConnectGoToSettingsDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setImage(@Nullable byte[]);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setImageUrl(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setInitialNotificationDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setInitialNotificationDescriptionNoAccount(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setInitialPairingDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setIntentUri(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setName(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setOpenCompanionAppDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setRetroactivePairingDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setSubsequentPairingDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setTriggerDistance(float);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setTrueWirelessImageUrlCase(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setTrueWirelessImageUrlLeftBud(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setTrueWirelessImageUrlRightBud(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setUnableToConnectDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setUnableToConnectTitle(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setUpdateCompanionAppDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDeviceMetadata.Builder setWaitLaunchCompanionAppDescription(@Nullable String);
+  }
+
+  public class FastPairDiscoveryItem {
+    method @Nullable public String getActionUrl();
+    method public int getActionUrlType();
+    method @Nullable public String getAppName();
+    method @Nullable public byte[] getAuthenticationPublicKeySecp256r1();
+    method @Nullable public String getDescription();
+    method @Nullable public String getDeviceName();
+    method @Nullable public String getDisplayUrl();
+    method public long getFirstObservationTimestampMillis();
+    method @Nullable public String getIconFfeUrl();
+    method @Nullable public byte[] getIconPng();
+    method @Nullable public String getId();
+    method public long getLastObservationTimestampMillis();
+    method @Nullable public String getMacAddress();
+    method @Nullable public String getPackageName();
+    method public long getPendingAppInstallTimestampMillis();
+    method public int getRssi();
+    method public int getState();
+    method @Nullable public String getTitle();
+    method @Nullable public String getTriggerId();
+    method public int getTxPower();
+  }
+
+  public static final class FastPairDiscoveryItem.Builder {
+    ctor public FastPairDiscoveryItem.Builder();
+    method @NonNull public android.nearby.FastPairDiscoveryItem build();
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setActionUrl(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setActionUrlType(int);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setAppName(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setAuthenticationPublicKeySecp256r1(@Nullable byte[]);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setDescription(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setDeviceName(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setDisplayUrl(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setFirstObservationTimestampMillis(long);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setIconFfeUrl(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setIconPng(@Nullable byte[]);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setId(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setLastObservationTimestampMillis(long);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setMacAddress(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setPackageName(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setPendingAppInstallTimestampMillis(long);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setRssi(int);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setState(int);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setTitle(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setTriggerId(@Nullable String);
+    method @NonNull public android.nearby.FastPairDiscoveryItem.Builder setTxPower(int);
+  }
+
+  public class FastPairEligibleAccount {
+    method @Nullable public android.accounts.Account getAccount();
+    method public boolean isOptIn();
+  }
+
+  public static final class FastPairEligibleAccount.Builder {
+    ctor public FastPairEligibleAccount.Builder();
+    method @NonNull public android.nearby.FastPairEligibleAccount build();
+    method @NonNull public android.nearby.FastPairEligibleAccount.Builder setAccount(@Nullable android.accounts.Account);
+    method @NonNull public android.nearby.FastPairEligibleAccount.Builder setOptIn(boolean);
+  }
+
   public abstract class NearbyDevice {
     method @NonNull public java.util.List<java.lang.Integer> getMediums();
     method @Nullable public String getName();
@@ -59,6 +261,9 @@
   }
 
   public class NearbyManager {
+    method public static boolean isFastPairScanEnabled(@NonNull android.content.Context);
+    method public void queryOffloadScanSupport(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setFastPairScanEnabled(@NonNull android.content.Context, boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
@@ -175,8 +380,11 @@
 
   public interface ScanCallback {
     method public void onDiscovered(@NonNull android.nearby.NearbyDevice);
+    method public default void onError(int);
     method public void onLost(@NonNull android.nearby.NearbyDevice);
     method public void onUpdated(@NonNull android.nearby.NearbyDevice);
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_UNSUPPORTED = 1; // 0x1
   }
 
   public abstract class ScanFilter {
diff --git a/nearby/README.md b/nearby/README.md
index 6925dc4..8451882 100644
--- a/nearby/README.md
+++ b/nearby/README.md
@@ -29,6 +29,20 @@
 $ aidegen .
 # This will launch Intellij project for Nearby module.
 ```
+Note, the setup above may fail to index classes defined in proto, such
+that all classes defined in proto shows red in IDE and cannot be auto-completed.
+To fix, you can mannually add jar files generated from proto to the class path
+as below.  First, find the jar file of presence proto with
+```sh
+ls $ANDROID_BUILD_TOP/out/soong/.intermediates/packages/modules/Connectivity/nearby/service/proto/presence-lite-protos/android_common/combined/presence-lite-protos.jar
+```
+Then, add the jar in IDE as below.
+1. Menu: File > Project Structure
+2. Select Modules at the left panel and select the Dependencies tab.
+3. Select the + icon and select 1 JARs or Directories option.
+4. Select the JAR file found above, make sure it is checked in the beginning square.
+5. Click the OK button.
+6. Restart the IDE to re-index.
 
 ## Build and Install
 
@@ -40,3 +54,23 @@
     --output /tmp/tethering.apex
 $ adb install -r /tmp/tethering.apex
 ```
+
+## Build and Install from tm-mainline-prod branch
+When build and flash the APEX from tm-mainline-prod, you may see the error below.
+```
+[INSTALL_FAILED_VERSION_DOWNGRADE: Downgrade of APEX package com.google.android.tethering is not allowed. Active version: 990090000 attempted: 339990000])
+```
+This is because the device is flashed with AOSP built from master or other branches, which has
+prebuilt APEX with higher version. We can use root access to replace the prebuilt APEX with the APEX
+built from tm-mainline-prod as below.
+1. adb root && adb remount; adb shell mount -orw,remount /system/apex
+2. cp tethering.next.apex com.google.android.tethering.apex
+3. adb push  com.google.android.tethering.apex  /system/apex/
+4. adb reboot
+After the steps above, the APEX can be reinstalled with adb install -r.
+(More APEX background can be found in https://source.android.com/docs/core/ota/apex#using-an-apex.)
+
+## Build APEX to support multiple platforms
+If you need to flash the APEX to different devices, Pixel 6, Pixel 7, or even devices from OEM, you
+can share the APEX by ```source build/envsetup.sh && lunch aosp_arm64-userdebug```. This can avoid
+ re-compiling for different targets.
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index e223b54..872606b 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -51,5 +51,7 @@
     static_libs: [
         "modules-utils-preconditions",
     ],
-    visibility: ["//packages/modules/Connectivity/nearby/tests:__subpackages__"],
+    visibility: [
+    "//packages/modules/Connectivity/nearby/tests:__subpackages__",
+    "//packages/modules/Connectivity/nearby/halfsheet:__subpackages__"],
 }
diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java
index 6fa5fb5..02548cb 100644
--- a/nearby/framework/java/android/nearby/DataElement.java
+++ b/nearby/framework/java/android/nearby/DataElement.java
@@ -16,13 +16,17 @@
 
 package android.nearby;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Represents a data element in Nearby Presence.
@@ -35,11 +39,94 @@
     private final int mKey;
     private final byte[] mValue;
 
+    /** @hide */
+    @IntDef({
+            DataType.BLE_SERVICE_DATA,
+            DataType.BLE_ADDRESS,
+            DataType.SALT,
+            DataType.PRIVATE_IDENTITY,
+            DataType.TRUSTED_IDENTITY,
+            DataType.PUBLIC_IDENTITY,
+            DataType.PROVISIONED_IDENTITY,
+            DataType.TX_POWER,
+            DataType.ACTION,
+            DataType.MODEL_ID,
+            DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER,
+            DataType.ACCOUNT_KEY_DATA,
+            DataType.CONNECTION_STATUS,
+            DataType.BATTERY,
+            DataType.SCAN_MODE,
+            DataType.TEST_DE_BEGIN,
+            DataType.TEST_DE_END
+    })
+    public @interface DataType {
+        int BLE_SERVICE_DATA = 100;
+        int BLE_ADDRESS = 101;
+        // This is to indicate if the scan is offload only
+        int SCAN_MODE = 102;
+        int SALT = 0;
+        int PRIVATE_IDENTITY = 1;
+        int TRUSTED_IDENTITY = 2;
+        int PUBLIC_IDENTITY = 3;
+        int PROVISIONED_IDENTITY = 4;
+        int TX_POWER = 5;
+        int ACTION = 6;
+        int MODEL_ID = 7;
+        int EDDYSTONE_EPHEMERAL_IDENTIFIER = 8;
+        int ACCOUNT_KEY_DATA = 9;
+        int CONNECTION_STATUS = 10;
+        int BATTERY = 11;
+        // Reserves test DE ranges from {@link DataElement.DataType#TEST_DE_BEGIN}
+        // to {@link DataElement.DataType#TEST_DE_END}, inclusive.
+        int TEST_DE_BEGIN = 256;
+        int TEST_DE_END = 260;
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean isValidType(int type) {
+        return type == DataType.BLE_SERVICE_DATA
+                || type == DataType.ACCOUNT_KEY_DATA
+                || type == DataType.BLE_ADDRESS
+                || type == DataType.SCAN_MODE
+                || type == DataType.SALT
+                || type == DataType.PRIVATE_IDENTITY
+                || type == DataType.TRUSTED_IDENTITY
+                || type == DataType.PUBLIC_IDENTITY
+                || type == DataType.PROVISIONED_IDENTITY
+                || type == DataType.TX_POWER
+                || type == DataType.ACTION
+                || type == DataType.MODEL_ID
+                || type == DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER
+                || type == DataType.CONNECTION_STATUS
+                || type == DataType.BATTERY;
+    }
+
+    /**
+     * @return {@code true} if this is identity type.
+     * @hide
+     */
+    public boolean isIdentityDataType() {
+        return mKey == DataType.PRIVATE_IDENTITY
+                || mKey == DataType.TRUSTED_IDENTITY
+                || mKey == DataType.PUBLIC_IDENTITY
+                || mKey == DataType.PROVISIONED_IDENTITY;
+    }
+
+    /**
+     * @return {@code true} if this is test data element type.
+     * @hide
+     */
+    public static boolean isTestDeType(int type) {
+        return type >= DataType.TEST_DE_BEGIN && type <= DataType.TEST_DE_END;
+    }
+
     /**
      * Constructs a {@link DataElement}.
      */
     public DataElement(int key, @NonNull byte[] value) {
-        Preconditions.checkState(value != null, "value cannot be null");
+        Preconditions.checkArgument(value != null, "value cannot be null");
         mKey = key;
         mValue = value;
     }
@@ -61,6 +148,20 @@
     };
 
     @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj instanceof DataElement) {
+            return mKey == ((DataElement) obj).mKey
+                    && Arrays.equals(mValue, ((DataElement) obj).mValue);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mKey, Arrays.hashCode(mValue));
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java
index d42fbf4..160ad75 100644
--- a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java
+++ b/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
 
 /**
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public class FastPairAccountKeyDeviceMetadata {
 
     FastPairAccountKeyDeviceMetadataParcel mMetadataParcel;
@@ -40,6 +42,7 @@
      * @return 16-byte Account Key.
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getDeviceAccountKey() {
         return mMetadataParcel.deviceAccountKey;
@@ -52,6 +55,7 @@
      * @return 32-byte Sha256 hash value.
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getSha256DeviceAccountKeyPublicAddress() {
         return mMetadataParcel.sha256DeviceAccountKeyPublicAddress;
@@ -62,6 +66,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public FastPairDeviceMetadata getFastPairDeviceMetadata() {
         if (mMetadataParcel.metadata == null) {
@@ -75,6 +80,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public FastPairDiscoveryItem getFastPairDiscoveryItem() {
         if (mMetadataParcel.discoveryItem == null) {
@@ -88,6 +94,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
 
         private final FastPairAccountKeyDeviceMetadataParcel mBuilderParcel;
@@ -97,6 +104,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder() {
             mBuilderParcel = new FastPairAccountKeyDeviceMetadataParcel();
             mBuilderParcel.deviceAccountKey = null;
@@ -113,6 +121,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDeviceAccountKey(@Nullable byte[] deviceAccountKey) {
             mBuilderParcel.deviceAccountKey = deviceAccountKey;
@@ -127,6 +136,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setSha256DeviceAccountKeyPublicAddress(
                 @Nullable byte[] sha256DeviceAccountKeyPublicAddress) {
@@ -143,6 +153,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
             if (metadata == null) {
@@ -160,6 +171,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setFastPairDiscoveryItem(@Nullable FastPairDiscoveryItem discoveryItem) {
             if (discoveryItem == null) {
@@ -175,6 +187,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public FastPairAccountKeyDeviceMetadata build() {
             return new FastPairAccountKeyDeviceMetadata(mBuilderParcel);
diff --git a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java
index 74831d5..1837671 100644
--- a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java
+++ b/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
 
 /**
@@ -24,6 +25,7 @@
  *
  * @hide
  */
+@SystemApi
 public class FastPairAntispoofKeyDeviceMetadata {
 
     FastPairAntispoofKeyDeviceMetadataParcel mMetadataParcel;
@@ -37,6 +39,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getAntispoofPublicKey() {
         return this.mMetadataParcel.antispoofPublicKey;
@@ -47,6 +50,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public FastPairDeviceMetadata getFastPairDeviceMetadata() {
         if (this.mMetadataParcel.deviceMetadata == null) {
@@ -60,6 +64,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
 
         private final FastPairAntispoofKeyDeviceMetadataParcel mBuilderParcel;
@@ -69,6 +74,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder() {
             mBuilderParcel = new FastPairAntispoofKeyDeviceMetadataParcel();
             mBuilderParcel.antispoofPublicKey = null;
@@ -82,6 +88,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setAntispoofPublicKey(@Nullable byte[] antispoofPublicKey) {
             mBuilderParcel.antispoofPublicKey = antispoofPublicKey;
@@ -96,6 +103,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
             if (metadata != null) {
@@ -111,6 +119,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public FastPairAntispoofKeyDeviceMetadata build() {
             return new FastPairAntispoofKeyDeviceMetadata(mBuilderParcel);
diff --git a/nearby/framework/java/android/nearby/FastPairDataProviderService.java b/nearby/framework/java/android/nearby/FastPairDataProviderService.java
index f1d5074..b6c840d 100644
--- a/nearby/framework/java/android/nearby/FastPairDataProviderService.java
+++ b/nearby/framework/java/android/nearby/FastPairDataProviderService.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
 import android.nearby.aidl.ByteArrayParcel;
@@ -58,6 +59,7 @@
  *
  * @hide
  */
+@SystemApi
 public abstract class FastPairDataProviderService extends Service {
     /**
      * The action the wrapping service should have in its intent filter to implement the
@@ -65,6 +67,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String ACTION_FAST_PAIR_DATA_PROVIDER =
             "android.nearby.action.FAST_PAIR_DATA_PROVIDER";
 
@@ -73,6 +76,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int MANAGE_REQUEST_ADD = 0;
 
     /**
@@ -80,6 +84,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int MANAGE_REQUEST_REMOVE = 1;
 
     /**
@@ -96,6 +101,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int ERROR_CODE_BAD_REQUEST = 0;
 
     /**
@@ -103,6 +109,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int ERROR_CODE_INTERNAL_ERROR = 1;
 
     /**
@@ -123,6 +130,7 @@
      * @param tag TAG for on device logging.
      * @hide
      */
+    @SystemApi
     public FastPairDataProviderService(@NonNull String tag) {
         mBinder = new Service();
         mTag = tag;
@@ -139,6 +147,7 @@
      *
      * @hide
      */
+    @SystemApi
     public interface FastPairAntispoofKeyDeviceMetadataCallback {
 
         /**
@@ -146,6 +155,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onFastPairAntispoofKeyDeviceMetadataReceived(
                 @NonNull FastPairAntispoofKeyDeviceMetadata metadata);
 
@@ -153,6 +163,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onError(@ErrorCode int code, @Nullable String message);
     }
 
@@ -161,6 +172,7 @@
      *
      * @hide
      */
+    @SystemApi
     public interface FastPairAccountDevicesMetadataCallback {
 
         /**
@@ -168,6 +180,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onFastPairAccountDevicesMetadataReceived(
                 @NonNull Collection<FastPairAccountKeyDeviceMetadata> metadatas);
         /**
@@ -175,6 +188,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onError(@ErrorCode int code, @Nullable String message);
     }
 
@@ -183,6 +197,7 @@
      *
      * @hide
      */
+    @SystemApi
     public interface FastPairEligibleAccountsCallback {
 
         /**
@@ -190,6 +205,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onFastPairEligibleAccountsReceived(
                 @NonNull Collection<FastPairEligibleAccount> accounts);
         /**
@@ -197,6 +213,7 @@
          *
          * @hide
          */
+        @SystemApi
         void onError(@ErrorCode int code, @Nullable String message);
     }
 
@@ -205,6 +222,7 @@
      *
      * @hide
      */
+    @SystemApi
     public interface FastPairManageActionCallback {
 
         /**
@@ -212,12 +230,14 @@
          *
          * @hide
          */
+        @SystemApi
         void onSuccess();
         /**
          * Invoked in case of error.
          *
          * @hide
          */
+        @SystemApi
         void onError(@ErrorCode int code, @Nullable String message);
     }
 
@@ -227,6 +247,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract void onLoadFastPairAntispoofKeyDeviceMetadata(
             @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
             @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback);
@@ -237,6 +258,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract void onLoadFastPairAccountDevicesMetadata(
             @NonNull FastPairAccountDevicesMetadataRequest request,
             @NonNull FastPairAccountDevicesMetadataCallback callback);
@@ -247,6 +269,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract void onLoadFastPairEligibleAccounts(
             @NonNull FastPairEligibleAccountsRequest request,
             @NonNull FastPairEligibleAccountsCallback callback);
@@ -256,6 +279,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract void onManageFastPairAccount(
             @NonNull FastPairManageAccountRequest request,
             @NonNull FastPairManageActionCallback callback);
@@ -265,6 +289,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract void onManageFastPairAccountDevice(
             @NonNull FastPairManageAccountDeviceRequest request,
             @NonNull FastPairManageActionCallback callback);
@@ -276,6 +301,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static class FastPairAntispoofKeyDeviceMetadataRequest {
 
         private final FastPairAntispoofKeyDeviceMetadataRequestParcel mMetadataRequestParcel;
@@ -295,6 +321,7 @@
          *         time.
          * @hide
          */
+        @SystemApi
         public @NonNull byte[] getModelId() {
             return this.mMetadataRequestParcel.modelId;
         }
@@ -315,6 +342,7 @@
      * needs to set account with a non-empty allow list.
      * @hide
      */
+    @SystemApi
     public static class FastPairAccountDevicesMetadataRequest {
 
         private final FastPairAccountDevicesMetadataRequestParcel mMetadataRequestParcel;
@@ -330,6 +358,7 @@
          * @return a FastPair account.
          * @hide
          */
+        @SystemApi
         public @NonNull Account getAccount() {
             return this.mMetadataRequestParcel.account;
         }
@@ -344,6 +373,7 @@
          * @return allowlist of Fast Pair devices using a collection of deviceAccountKeys.
          * @hide
          */
+        @SystemApi
         public @NonNull Collection<byte[]> getDeviceAccountKeys()  {
             if (this.mMetadataRequestParcel.deviceAccountKeys == null) {
                 return new ArrayList<byte[]>(0);
@@ -363,6 +393,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static class FastPairEligibleAccountsRequest {
         @SuppressWarnings("UnusedVariable")
         private final FastPairEligibleAccountsRequestParcel mAccountsRequestParcel;
@@ -381,6 +412,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static class FastPairManageAccountRequest {
 
         private final FastPairManageAccountRequestParcel mAccountRequestParcel;
@@ -395,6 +427,7 @@
          *
          * @hide
          */
+        @SystemApi
         public @ManageRequestType int getRequestType() {
             return this.mAccountRequestParcel.requestType;
         }
@@ -403,6 +436,7 @@
          *
          * @hide
          */
+        @SystemApi
         public @NonNull Account getAccount() {
             return this.mAccountRequestParcel.account;
         }
@@ -416,6 +450,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static class FastPairManageAccountDeviceRequest {
 
         private final FastPairManageAccountDeviceRequestParcel mRequestParcel;
@@ -430,6 +465,7 @@
          *
          * @hide
          */
+        @SystemApi
         public @ManageRequestType int getRequestType() {
             return this.mRequestParcel.requestType;
         }
@@ -438,6 +474,7 @@
          *
          * @hide
          */
+        @SystemApi
         public @NonNull Account getAccount() {
             return this.mRequestParcel.account;
         }
@@ -446,6 +483,7 @@
          *
          * @hide
          */
+        @SystemApi
         public @NonNull FastPairAccountKeyDeviceMetadata getAccountKeyDeviceMetadata() {
             return new FastPairAccountKeyDeviceMetadata(
                     this.mRequestParcel.accountKeyDeviceMetadata);
diff --git a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
index 0e2e79d..04845c0 100644
--- a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
+++ b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.nearby.aidl.FastPairDeviceMetadataParcel;
 
 /**
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public class FastPairDeviceMetadata {
 
     FastPairDeviceMetadataParcel mMetadataParcel;
@@ -39,6 +41,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getImageUrl() {
         return mMetadataParcel.imageUrl;
@@ -49,6 +52,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getIntentUri() {
         return mMetadataParcel.intentUri;
@@ -60,6 +64,7 @@
      *
      * @hide
      */
+    @SystemApi
     public int getBleTxPower() {
         return mMetadataParcel.bleTxPower;
     }
@@ -69,6 +74,7 @@
      *
      * @hide
      */
+    @SystemApi
     public float getTriggerDistance() {
         return mMetadataParcel.triggerDistance;
     }
@@ -80,6 +86,7 @@
      * @return Fast Pair device image in 32-bit PNG with dimensions of 512px by 512px.
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getImage() {
         return mMetadataParcel.image;
@@ -92,6 +99,7 @@
      * TRUE_WIRELESS_HEADPHONES = 7;
      * @hide
      */
+    @SystemApi
     public int getDeviceType() {
         return mMetadataParcel.deviceType;
     }
@@ -101,6 +109,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getName() {
         return mMetadataParcel.name;
@@ -111,6 +120,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getTrueWirelessImageUrlLeftBud() {
         return mMetadataParcel.trueWirelessImageUrlLeftBud;
@@ -121,6 +131,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getTrueWirelessImageUrlRightBud() {
         return mMetadataParcel.trueWirelessImageUrlRightBud;
@@ -131,6 +142,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getTrueWirelessImageUrlCase() {
         return mMetadataParcel.trueWirelessImageUrlCase;
@@ -142,6 +154,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getInitialNotificationDescription() {
         return mMetadataParcel.initialNotificationDescription;
@@ -153,6 +166,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getInitialNotificationDescriptionNoAccount() {
         return mMetadataParcel.initialNotificationDescriptionNoAccount;
@@ -164,6 +178,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getOpenCompanionAppDescription() {
         return mMetadataParcel.openCompanionAppDescription;
@@ -175,6 +190,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getUpdateCompanionAppDescription() {
         return mMetadataParcel.updateCompanionAppDescription;
@@ -186,6 +202,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getDownloadCompanionAppDescription() {
         return mMetadataParcel.downloadCompanionAppDescription;
@@ -206,6 +223,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getUnableToConnectDescription() {
         return mMetadataParcel.unableToConnectDescription;
@@ -217,6 +235,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getInitialPairingDescription() {
         return mMetadataParcel.initialPairingDescription;
@@ -228,6 +247,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getConnectSuccessCompanionAppInstalled() {
         return mMetadataParcel.connectSuccessCompanionAppInstalled;
@@ -239,6 +259,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getConnectSuccessCompanionAppNotInstalled() {
         return mMetadataParcel.connectSuccessCompanionAppNotInstalled;
@@ -250,6 +271,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getSubsequentPairingDescription() {
         return mMetadataParcel.subsequentPairingDescription;
@@ -261,6 +283,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getRetroactivePairingDescription() {
         return mMetadataParcel.retroactivePairingDescription;
@@ -272,6 +295,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getWaitLaunchCompanionAppDescription() {
         return mMetadataParcel.waitLaunchCompanionAppDescription;
@@ -283,6 +307,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getFailConnectGoToSettingsDescription() {
         return mMetadataParcel.failConnectGoToSettingsDescription;
@@ -293,6 +318,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
 
         private final FastPairDeviceMetadataParcel mBuilderParcel;
@@ -302,6 +328,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder() {
             mBuilderParcel = new FastPairDeviceMetadataParcel();
             mBuilderParcel.imageUrl = null;
@@ -337,6 +364,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setImageUrl(@Nullable String imageUrl) {
             mBuilderParcel.imageUrl = imageUrl;
@@ -350,6 +378,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setIntentUri(@Nullable String intentUri) {
             mBuilderParcel.intentUri = intentUri;
@@ -363,6 +392,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setName(@Nullable String name) {
             mBuilderParcel.name = name;
@@ -376,6 +406,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setBleTxPower(int bleTxPower) {
             mBuilderParcel.bleTxPower = bleTxPower;
@@ -389,6 +420,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTriggerDistance(float triggerDistance) {
             mBuilderParcel.triggerDistance = triggerDistance;
@@ -404,6 +436,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setImage(@Nullable byte[] image) {
             mBuilderParcel.image = image;
@@ -417,6 +450,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDeviceType(int deviceType) {
             mBuilderParcel.deviceType = deviceType;
@@ -430,6 +464,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTrueWirelessImageUrlLeftBud(
                 @Nullable String trueWirelessImageUrlLeftBud) {
@@ -444,6 +479,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTrueWirelessImageUrlRightBud(
                 @Nullable String trueWirelessImageUrlRightBud) {
@@ -458,6 +494,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTrueWirelessImageUrlCase(@Nullable String trueWirelessImageUrlCase) {
             mBuilderParcel.trueWirelessImageUrlCase = trueWirelessImageUrlCase;
@@ -471,6 +508,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setInitialNotificationDescription(
                 @Nullable String initialNotificationDescription) {
@@ -486,6 +524,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setInitialNotificationDescriptionNoAccount(
                 @Nullable String initialNotificationDescriptionNoAccount) {
@@ -501,6 +540,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setOpenCompanionAppDescription(
                 @Nullable String openCompanionAppDescription) {
@@ -515,6 +555,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setUpdateCompanionAppDescription(
                 @Nullable String updateCompanionAppDescription) {
@@ -529,6 +570,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDownloadCompanionAppDescription(
                 @Nullable String downloadCompanionAppDescription) {
@@ -543,6 +585,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setUnableToConnectTitle(@Nullable String unableToConnectTitle) {
             mBuilderParcel.unableToConnectTitle = unableToConnectTitle;
@@ -557,6 +600,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setUnableToConnectDescription(
                 @Nullable String unableToConnectDescription) {
@@ -571,6 +615,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setInitialPairingDescription(@Nullable String initialPairingDescription) {
             mBuilderParcel.initialPairingDescription = initialPairingDescription;
@@ -585,6 +630,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setConnectSuccessCompanionAppInstalled(
                 @Nullable String connectSuccessCompanionAppInstalled) {
@@ -601,6 +647,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setConnectSuccessCompanionAppNotInstalled(
                 @Nullable String connectSuccessCompanionAppNotInstalled) {
@@ -617,6 +664,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setSubsequentPairingDescription(
                 @Nullable String subsequentPairingDescription) {
@@ -631,6 +679,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setRetroactivePairingDescription(
                 @Nullable String retroactivePairingDescription) {
@@ -646,6 +695,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setWaitLaunchCompanionAppDescription(
                 @Nullable String waitLaunchCompanionAppDescription) {
@@ -662,6 +712,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setFailConnectGoToSettingsDescription(
                 @Nullable String failConnectGoToSettingsDescription) {
@@ -675,6 +726,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public FastPairDeviceMetadata build() {
             return new FastPairDeviceMetadata(mBuilderParcel);
diff --git a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java b/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java
index d8dfe29..ce7413a 100644
--- a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java
+++ b/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.nearby.aidl.FastPairDiscoveryItemParcel;
 
 /**
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public class FastPairDiscoveryItem {
 
     FastPairDiscoveryItemParcel mMetadataParcel;
@@ -39,6 +41,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getId() {
         return mMetadataParcel.id;
@@ -49,6 +52,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getMacAddress() {
         return mMetadataParcel.macAddress;
@@ -59,6 +63,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getActionUrl() {
         return mMetadataParcel.actionUrl;
@@ -69,6 +74,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getDeviceName() {
         return mMetadataParcel.deviceName;
@@ -79,6 +85,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getTitle() {
         return mMetadataParcel.title;
@@ -89,6 +96,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getDescription() {
         return mMetadataParcel.description;
@@ -99,6 +107,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getDisplayUrl() {
         return mMetadataParcel.displayUrl;
@@ -109,6 +118,7 @@
      *
      * @hide
      */
+    @SystemApi
     public long getLastObservationTimestampMillis() {
         return mMetadataParcel.lastObservationTimestampMillis;
     }
@@ -118,6 +128,7 @@
      *
      * @hide
      */
+    @SystemApi
     public long getFirstObservationTimestampMillis() {
         return mMetadataParcel.firstObservationTimestampMillis;
     }
@@ -127,6 +138,7 @@
      *
      * @hide
      */
+    @SystemApi
     public int getState() {
         return mMetadataParcel.state;
     }
@@ -136,6 +148,7 @@
      *
      * @hide
      */
+    @SystemApi
     public int getActionUrlType() {
         return mMetadataParcel.actionUrlType;
     }
@@ -145,6 +158,7 @@
      *
      * @hide
      */
+    @SystemApi
     public int getRssi() {
         return mMetadataParcel.rssi;
     }
@@ -154,6 +168,7 @@
      *
      * @hide
      */
+    @SystemApi
     public long getPendingAppInstallTimestampMillis() {
         return mMetadataParcel.pendingAppInstallTimestampMillis;
     }
@@ -163,6 +178,7 @@
      *
      * @hide
      */
+    @SystemApi
     public int getTxPower() {
         return mMetadataParcel.txPower;
     }
@@ -172,6 +188,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getAppName() {
         return mMetadataParcel.appName;
@@ -182,6 +199,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getPackageName() {
         return mMetadataParcel.packageName;
@@ -192,6 +210,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getTriggerId() {
         return mMetadataParcel.triggerId;
@@ -204,6 +223,7 @@
      * @return IconPng in 32-bit PNG with dimensions of 512px by 512px.
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getIconPng() {
         return mMetadataParcel.iconPng;
@@ -214,6 +234,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getIconFfeUrl() {
         return mMetadataParcel.iconFifeUrl;
@@ -226,6 +247,7 @@
      * @return 64-byte authenticationPublicKeySecp256r1.
      * @hide
      */
+    @SystemApi
     @Nullable
     public byte[] getAuthenticationPublicKeySecp256r1() {
         return mMetadataParcel.authenticationPublicKeySecp256r1;
@@ -236,6 +258,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
 
         private final FastPairDiscoveryItemParcel mBuilderParcel;
@@ -245,6 +268,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder() {
             mBuilderParcel = new FastPairDiscoveryItemParcel();
         }
@@ -257,6 +281,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setId(@Nullable String id) {
             mBuilderParcel.id = id;
@@ -270,6 +295,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setMacAddress(@Nullable String macAddress) {
             mBuilderParcel.macAddress = macAddress;
@@ -283,6 +309,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setActionUrl(@Nullable String actionUrl) {
             mBuilderParcel.actionUrl = actionUrl;
@@ -295,6 +322,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDeviceName(@Nullable String deviceName) {
             mBuilderParcel.deviceName = deviceName;
@@ -308,6 +336,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTitle(@Nullable String title) {
             mBuilderParcel.title = title;
@@ -321,6 +350,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDescription(@Nullable String description) {
             mBuilderParcel.description = description;
@@ -334,6 +364,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDisplayUrl(@Nullable String displayUrl) {
             mBuilderParcel.displayUrl = displayUrl;
@@ -348,6 +379,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setLastObservationTimestampMillis(
                 long lastObservationTimestampMillis) {
@@ -363,6 +395,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setFirstObservationTimestampMillis(
                 long firstObservationTimestampMillis) {
@@ -377,6 +410,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setState(int state) {
             mBuilderParcel.state = state;
@@ -390,6 +424,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setActionUrlType(int actionUrlType) {
             mBuilderParcel.actionUrlType = actionUrlType;
@@ -403,6 +438,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setRssi(int rssi) {
             mBuilderParcel.rssi = rssi;
@@ -417,6 +453,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setPendingAppInstallTimestampMillis(long pendingAppInstallTimestampMillis) {
             mBuilderParcel.pendingAppInstallTimestampMillis = pendingAppInstallTimestampMillis;
@@ -430,6 +467,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTxPower(int txPower) {
             mBuilderParcel.txPower = txPower;
@@ -443,6 +481,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setAppName(@Nullable String appName) {
             mBuilderParcel.appName = appName;
@@ -456,6 +495,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setPackageName(@Nullable String packageName) {
             mBuilderParcel.packageName = packageName;
@@ -469,6 +509,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setTriggerId(@Nullable String triggerId) {
             mBuilderParcel.triggerId = triggerId;
@@ -482,6 +523,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setIconPng(@Nullable byte[] iconPng) {
             mBuilderParcel.iconPng = iconPng;
@@ -495,6 +537,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setIconFfeUrl(@Nullable String iconFifeUrl) {
             mBuilderParcel.iconFifeUrl = iconFifeUrl;
@@ -509,6 +552,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setAuthenticationPublicKeySecp256r1(
                 @Nullable byte[] authenticationPublicKeySecp256r1) {
@@ -521,6 +565,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public FastPairDiscoveryItem build() {
             return new FastPairDiscoveryItem(mBuilderParcel);
diff --git a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java b/nearby/framework/java/android/nearby/FastPairEligibleAccount.java
index 8be4cca..e6c3047 100644
--- a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java
+++ b/nearby/framework/java/android/nearby/FastPairEligibleAccount.java
@@ -19,6 +19,7 @@
 import android.accounts.Account;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.nearby.aidl.FastPairEligibleAccountParcel;
 
 /**
@@ -26,6 +27,7 @@
  *
  * @hide
  */
+@SystemApi
 public class FastPairEligibleAccount {
 
     FastPairEligibleAccountParcel mAccountParcel;
@@ -39,6 +41,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public Account getAccount() {
         return this.mAccountParcel.account;
@@ -49,6 +52,7 @@
      *
      * @hide
      */
+    @SystemApi
     public boolean isOptIn() {
         return this.mAccountParcel.optIn;
     }
@@ -58,6 +62,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
 
         private final FastPairEligibleAccountParcel mBuilderParcel;
@@ -67,6 +72,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder() {
             mBuilderParcel = new FastPairEligibleAccountParcel();
             mBuilderParcel.account = null;
@@ -80,6 +86,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setAccount(@Nullable Account account) {
             mBuilderParcel.account = account;
@@ -93,6 +100,7 @@
          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setOptIn(boolean optIn) {
             mBuilderParcel.optIn = optIn;
@@ -104,6 +112,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public FastPairEligibleAccount build() {
             return new FastPairEligibleAccount(mBuilderParcel);
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 0291fff..402e3a6 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -20,6 +20,7 @@
 import android.nearby.IScanListener;
 import android.nearby.BroadcastRequestParcelable;
 import android.nearby.ScanRequest;
+import android.nearby.aidl.IOffloadCallback;
 
 /**
  * Interface for communicating with the nearby services.
@@ -37,4 +38,6 @@
             in IBroadcastListener callback, String packageName, @nullable String attributionTag);
 
     void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag);
+
+    void queryOffloadScanSupport(in IOffloadCallback callback) ;
 }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
index 538940c..e8fcc28 100644
--- a/nearby/framework/java/android/nearby/NearbyDevice.java
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -21,11 +21,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.util.ArraySet;
 
 import com.android.internal.util.Preconditions;
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A class represents a device that can be discovered by multiple mediums.
@@ -123,13 +125,17 @@
 
     @Override
     public boolean equals(Object other) {
-        if (other instanceof NearbyDevice) {
-            NearbyDevice otherDevice = (NearbyDevice) other;
-            return Objects.equals(mName, otherDevice.mName)
-                    && mMediums == otherDevice.mMediums
-                    && mRssi == otherDevice.mRssi;
+        if (!(other instanceof NearbyDevice)) {
+            return false;
         }
-        return false;
+        NearbyDevice otherDevice = (NearbyDevice) other;
+        Set<Integer> mediumSet = new ArraySet<>(mMediums);
+        Set<Integer> otherMediumSet = new ArraySet<>(otherDevice.mMediums);
+        if (!mediumSet.equals(otherMediumSet)) {
+            return false;
+        }
+
+        return Objects.equals(mName, otherDevice.mName) && mRssi == otherDevice.mRssi;
     }
 
     @Override
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 8f44091..180b662 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -76,6 +76,17 @@
                         in.readByteArray(salt);
                         builder.setData(salt);
                     }
+                    if (in.readInt() == 1) {
+                        builder.setPresenceDevice(in.readParcelable(
+                                PresenceDevice.class.getClassLoader(),
+                                PresenceDevice.class));
+                    }
+                    if (in.readInt() == 1) {
+                        int encryptionKeyTagLength = in.readInt();
+                        byte[] keyTag = new byte[encryptionKeyTagLength];
+                        in.readByteArray(keyTag);
+                        builder.setData(keyTag);
+                    }
                     return builder.build();
                 }
 
@@ -96,6 +107,8 @@
     @Nullable private final String mFastPairModelId;
     @Nullable private final byte[] mData;
     @Nullable private final byte[] mSalt;
+    @Nullable private final PresenceDevice mPresenceDevice;
+    @Nullable private final byte[] mEncryptionKeyTag;
 
     private NearbyDeviceParcelable(
             @ScanRequest.ScanType int scanType,
@@ -108,7 +121,9 @@
             @Nullable String fastPairModelId,
             @Nullable String bluetoothAddress,
             @Nullable byte[] data,
-            @Nullable byte[] salt) {
+            @Nullable byte[] salt,
+            @Nullable PresenceDevice presenceDevice,
+            @Nullable byte[] encryptionKeyTag) {
         mScanType = scanType;
         mName = name;
         mMedium = medium;
@@ -120,6 +135,8 @@
         mBluetoothAddress = bluetoothAddress;
         mData = data;
         mSalt = salt;
+        mPresenceDevice = presenceDevice;
+        mEncryptionKeyTag = encryptionKeyTag;
     }
 
     /** No special parcel contents. */
@@ -164,6 +181,15 @@
             dest.writeInt(mSalt.length);
             dest.writeByteArray(mSalt);
         }
+        dest.writeInt(mPresenceDevice == null ? 0 : 1);
+        if (mPresenceDevice != null) {
+            dest.writeParcelable(mPresenceDevice, /* parcelableFlags= */ 0);
+        }
+        dest.writeInt(mEncryptionKeyTag == null ? 0 : 1);
+        if (mEncryptionKeyTag != null) {
+            dest.writeInt(mEncryptionKeyTag.length);
+            dest.writeByteArray(mEncryptionKeyTag);
+        }
     }
 
     /** Returns a string representation of this ScanRequest. */
@@ -210,7 +236,11 @@
                     && (Objects.equals(
                             mFastPairModelId, otherNearbyDeviceParcelable.mFastPairModelId))
                     && (Arrays.equals(mData, otherNearbyDeviceParcelable.mData))
-                    && (Arrays.equals(mSalt, otherNearbyDeviceParcelable.mSalt));
+                    && (Arrays.equals(mSalt, otherNearbyDeviceParcelable.mSalt))
+                    && (Objects.equals(
+                            mPresenceDevice, otherNearbyDeviceParcelable.mPresenceDevice))
+                    && (Arrays.equals(
+                            mEncryptionKeyTag, otherNearbyDeviceParcelable.mEncryptionKeyTag));
         }
         return false;
     }
@@ -227,7 +257,9 @@
                 mBluetoothAddress,
                 mFastPairModelId,
                 Arrays.hashCode(mData),
-                Arrays.hashCode(mSalt));
+                Arrays.hashCode(mSalt),
+                mPresenceDevice,
+                Arrays.hashCode(mEncryptionKeyTag));
     }
 
     /**
@@ -351,6 +383,26 @@
         return mSalt;
     }
 
+    /**
+     * Gets the {@link PresenceDevice} Nearby Presence device. This field is
+     * for Fast Pair client only.
+     */
+    @Nullable
+    public PresenceDevice getPresenceDevice() {
+        return mPresenceDevice;
+    }
+
+    /**
+     * Gets the encryption key tag calculated from advertisement
+     * Returns {@code null} if the data is not encrypted or this is not a Presence device.
+     *
+     * Used in Presence.
+     */
+    @Nullable
+    public byte[] getEncryptionKeyTag() {
+        return mEncryptionKeyTag;
+    }
+
     /** Builder class for {@link NearbyDeviceParcelable}. */
     public static final class Builder {
         @Nullable private String mName;
@@ -364,6 +416,8 @@
         @Nullable private String mBluetoothAddress;
         @Nullable private byte[] mData;
         @Nullable private byte[] mSalt;
+        @Nullable private PresenceDevice mPresenceDevice;
+        @Nullable private byte[] mEncryptionKeyTag;
 
         /**
          * Sets the scan type of the NearbyDeviceParcelable.
@@ -469,7 +523,7 @@
         /**
          * Sets the scanned raw data.
          *
-         * @param data Data the scan. For example, {@link ScanRecord#getServiceData()} if scanned by
+         * @param data raw data scanned, like {@link ScanRecord#getServiceData()} if scanned by
          *             Bluetooth.
          */
         @NonNull
@@ -479,6 +533,17 @@
         }
 
         /**
+         * Sets the encryption key tag calculated from the advertisement.
+         *
+         * @param encryptionKeyTag calculated from identity scanned from the advertisement
+         */
+        @NonNull
+        public Builder setEncryptionKeyTag(@Nullable byte[] encryptionKeyTag) {
+            mEncryptionKeyTag = encryptionKeyTag;
+            return this;
+        }
+
+        /**
          * Sets the slat in the advertisement from the Nearby Presence device.
          *
          * @param salt in the advertisement from the Nearby Presence device.
@@ -489,6 +554,17 @@
             return this;
         }
 
+        /**
+         * Sets the {@link PresenceDevice} if there is any.
+         * The current {@link NearbyDeviceParcelable} can be seen as the wrapper of the
+         * {@link PresenceDevice}.
+         */
+        @Nullable
+        public Builder setPresenceDevice(@Nullable PresenceDevice presenceDevice) {
+            mPresenceDevice = presenceDevice;
+            return this;
+        }
+
         /** Builds a ScanResult. */
         @NonNull
         public NearbyDeviceParcelable build() {
@@ -503,7 +579,9 @@
                     mFastPairModelId,
                     mBluetoothAddress,
                     mData,
-                    mSalt);
+                    mSalt,
+                    mPresenceDevice,
+                    mEncryptionKeyTag);
         }
     }
 }
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 106c290..4147c9a 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.nearby.aidl.IOffloadCallback;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Log;
@@ -37,6 +38,7 @@
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * This class provides a way to perform Nearby related operations such as scanning, broadcasting
@@ -62,7 +64,7 @@
             ScanStatus.ERROR,
     })
     public @interface ScanStatus {
-        // Default, invalid state.
+        // The undetermined status, some modules may be initializing. Retry is suggested.
         int UNKNOWN = 0;
         // The successful state.
         int SUCCESS = 1;
@@ -103,6 +105,9 @@
         mService = service;
     }
 
+    // This can be null when NearbyDeviceParcelable field not set for Presence device
+    // or the scan type is not recognized.
+    @Nullable
     private static NearbyDevice toClientNearbyDevice(
             NearbyDeviceParcelable nearbyDeviceParcelable,
             @ScanRequest.ScanType int scanType) {
@@ -118,23 +123,12 @@
         }
 
         if (scanType == ScanRequest.SCAN_TYPE_NEARBY_PRESENCE) {
-            PublicCredential publicCredential = nearbyDeviceParcelable.getPublicCredential();
-            if (publicCredential == null) {
-                return null;
+            PresenceDevice presenceDevice = nearbyDeviceParcelable.getPresenceDevice();
+            if (presenceDevice == null) {
+                Log.e(TAG,
+                        "Cannot find any Presence device in discovered NearbyDeviceParcelable");
             }
-            byte[] salt = nearbyDeviceParcelable.getSalt();
-            if (salt == null) {
-                salt = new byte[0];
-            }
-            return new PresenceDevice.Builder(
-                    // Use the public credential hash as the device Id.
-                    String.valueOf(publicCredential.hashCode()),
-                    salt,
-                    publicCredential.getSecretId(),
-                    publicCredential.getEncryptedMetadata())
-                    .setRssi(nearbyDeviceParcelable.getRssi())
-                    .addMedium(nearbyDeviceParcelable.getMedium())
-                    .build();
+            return presenceDevice;
         }
         return null;
     }
@@ -278,29 +272,72 @@
     }
 
     /**
+     * Query if offload scan is available in a device. The query is asynchronous and
+     * result is called back in {@link Consumer}, which is set to true if offload is supported.
+     *
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked with {@code true} if offload is supported
+     */
+    public void queryOffloadScanSupport(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> callback) {
+        try {
+            mService.queryOffloadScanSupport(new OffloadTransport(executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Read from {@link Settings} whether Fast Pair scan is enabled.
      *
-     * @param context the {@link Context} to query the setting
-     * @return whether the Fast Pair is enabled
-     * @hide
+     * @param context either activity or application context for caller to query the setting
+     * @return whether the Fast Pair scan is enabled
+     * @throws NullPointerException if {@code context} is {@code null}
      */
-    public static boolean getFastPairScanEnabled(@NonNull Context context) {
+    public static boolean isFastPairScanEnabled(@NonNull Context context) {
+        Objects.requireNonNull(context);
         final int enabled = Settings.Secure.getInt(
                 context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, 0);
         return enabled != 0;
     }
 
     /**
-     * Write into {@link Settings} whether Fast Pair scan is enabled
+     * Write into {@link Settings} whether Fast Pair scan is enabled.
      *
-     * @param context the {@link Context} to set the setting
+     * @param context either activity or application context, for caller to set the setting
      * @param enable whether the Fast Pair scan should be enabled
-     * @hide
+     * @throws NullPointerException if {@code context} is {@code null}
      */
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) {
+        Objects.requireNonNull(context);
         Settings.Secure.putInt(
                 context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0);
+        Log.v(TAG, String.format(
+                "successfully %s Fast Pair scan", enable ? "enables" : "disables"));
+    }
+
+    private static class OffloadTransport extends IOffloadCallback.Stub {
+
+        private final Executor mExecutor;
+        // Null when cancelled
+        volatile @Nullable Consumer<Boolean> mConsumer;
+
+        OffloadTransport(Executor executor, Consumer<Boolean> consumer) {
+            Preconditions.checkArgument(executor != null, "illegal null executor");
+            Preconditions.checkArgument(consumer != null, "illegal null consumer");
+            mExecutor = executor;
+            mConsumer = consumer;
+        }
+
+        @Override
+        public void onQueryComplete(boolean isOffloadSupported) {
+            mExecutor.execute(() -> {
+                if (mConsumer != null) {
+                    mConsumer.accept(isOffloadSupported);
+                }
+            });
+        }
     }
 
     private static class ScanListenerTransport extends IScanListener.Stub {
@@ -339,9 +376,9 @@
         public void onDiscovered(NearbyDeviceParcelable nearbyDeviceParcelable)
                 throws RemoteException {
             mExecutor.execute(() -> {
-                if (mScanCallback != null) {
-                    mScanCallback.onDiscovered(
-                            toClientNearbyDevice(nearbyDeviceParcelable, mScanType));
+                NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType);
+                if (mScanCallback != null && nearbyDevice != null) {
+                    mScanCallback.onDiscovered(nearbyDevice);
                 }
             });
         }
@@ -350,7 +387,8 @@
         public void onUpdated(NearbyDeviceParcelable nearbyDeviceParcelable)
                 throws RemoteException {
             mExecutor.execute(() -> {
-                if (mScanCallback != null) {
+                NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType);
+                if (mScanCallback != null && nearbyDevice != null) {
                     mScanCallback.onUpdated(
                             toClientNearbyDevice(nearbyDeviceParcelable, mScanType));
                 }
@@ -360,7 +398,8 @@
         @Override
         public void onLost(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException {
             mExecutor.execute(() -> {
-                if (mScanCallback != null) {
+                NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType);
+                if (mScanCallback != null && nearbyDevice != null) {
                     mScanCallback.onLost(
                             toClientNearbyDevice(nearbyDeviceParcelable, mScanType));
                 }
diff --git a/nearby/framework/java/android/nearby/PresenceDevice.java b/nearby/framework/java/android/nearby/PresenceDevice.java
index cb406e4..b5d9ad4 100644
--- a/nearby/framework/java/android/nearby/PresenceDevice.java
+++ b/nearby/framework/java/android/nearby/PresenceDevice.java
@@ -26,6 +26,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -134,6 +135,54 @@
         return mExtendedProperties;
     }
 
+    /**
+     * This can only be hidden because this is the System API,
+     * which cannot be changed in T timeline.
+     *
+     * @hide
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof PresenceDevice) {
+            PresenceDevice otherDevice = (PresenceDevice) other;
+            if (super.equals(otherDevice)) {
+                return Arrays.equals(mSalt, otherDevice.mSalt)
+                        && Arrays.equals(mSecretId, otherDevice.mSecretId)
+                        && Arrays.equals(mEncryptedIdentity, otherDevice.mEncryptedIdentity)
+                        && Objects.equals(mDeviceId, otherDevice.mDeviceId)
+                        && mDeviceType == otherDevice.mDeviceType
+                        && Objects.equals(mDeviceImageUrl, otherDevice.mDeviceImageUrl)
+                        && mDiscoveryTimestampMillis == otherDevice.mDiscoveryTimestampMillis
+                        && Objects.equals(mExtendedProperties, otherDevice.mExtendedProperties);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This can only be hidden because this is the System API,
+     * which cannot be changed in T timeline.
+     *
+     * @hide
+     *
+     * @return The unique hash value of the {@link PresenceDevice}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                getName(),
+                getMediums(),
+                getRssi(),
+                Arrays.hashCode(mSalt),
+                Arrays.hashCode(mSecretId),
+                Arrays.hashCode(mEncryptedIdentity),
+                mDeviceId,
+                mDeviceType,
+                mDeviceImageUrl,
+                mDiscoveryTimestampMillis,
+                mExtendedProperties);
+    }
+
     private PresenceDevice(String deviceName, List<Integer> mMediums, int rssi, String deviceId,
             byte[] salt, byte[] secretId, byte[] encryptedIdentity, int deviceType,
             String deviceImageUrl, long discoveryTimestampMillis,
@@ -326,7 +375,6 @@
             return this;
         }
 
-
         /**
          * Sets the image url of the discovered Presence device.
          *
@@ -338,7 +386,6 @@
             return this;
         }
 
-
         /**
          * Sets discovery timestamp, the clock is based on elapsed time.
          *
@@ -350,7 +397,6 @@
             return this;
         }
 
-
         /**
          * Adds an extended property of the discovered presence device.
          *
diff --git a/nearby/framework/java/android/nearby/PresenceScanFilter.java b/nearby/framework/java/android/nearby/PresenceScanFilter.java
index f0c3c06..50e97b4 100644
--- a/nearby/framework/java/android/nearby/PresenceScanFilter.java
+++ b/nearby/framework/java/android/nearby/PresenceScanFilter.java
@@ -71,7 +71,7 @@
         super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, rssiThreshold);
         mCredentials = new ArrayList<>(credentials);
         mPresenceActions = new ArrayList<>(presenceActions);
-        mExtendedProperties = extendedProperties;
+        mExtendedProperties = new ArrayList<>(extendedProperties);
     }
 
     private PresenceScanFilter(Parcel in) {
@@ -132,7 +132,7 @@
         }
         dest.writeInt(mExtendedProperties.size());
         if (!mExtendedProperties.isEmpty()) {
-            dest.writeList(mExtendedProperties);
+            dest.writeParcelableList(mExtendedProperties, 0);
         }
     }
 
diff --git a/nearby/framework/java/android/nearby/ScanCallback.java b/nearby/framework/java/android/nearby/ScanCallback.java
index 1b1b4bc..4ac62b2 100644
--- a/nearby/framework/java/android/nearby/ScanCallback.java
+++ b/nearby/framework/java/android/nearby/ScanCallback.java
@@ -16,9 +16,13 @@
 
 package android.nearby;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Reports newly discovered devices.
  * Note: The frequency of the callback is dependent on whether the caller
@@ -31,6 +35,21 @@
  */
 @SystemApi
 public interface ScanCallback {
+
+    /** General error code for scan. */
+    int ERROR_UNKNOWN = 0;
+
+    /**
+     * Scan failed as the request is not supported.
+     */
+    int ERROR_UNSUPPORTED = 1;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_UNKNOWN, ERROR_UNSUPPORTED})
+    @interface ErrorCode {
+    }
+
     /**
      * Reports a {@link NearbyDevice} being discovered.
      *
@@ -51,4 +70,11 @@
      * @param device {@link NearbyDevice} that is lost.
      */
     void onLost(@NonNull NearbyDevice device);
+
+    /**
+     * Notifies clients of error from the scan.
+     *
+     * @param errorCode defined by Nearby
+     */
+    default void onError(@ErrorCode int errorCode) {}
 }
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index c717ac7..9421820 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -62,6 +62,12 @@
      */
     public static final int SCAN_MODE_NO_POWER = -1;
     /**
+     * A special scan mode to indicate that client only wants to use CHRE to scan.
+     *
+     * @hide
+     */
+    public static final int SCAN_MODE_CHRE_ONLY = 3;
+    /**
      * Used to read a ScanRequest from a Parcel.
      */
     @NonNull
diff --git a/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl b/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl
new file mode 100644
index 0000000..f353aff
--- /dev/null
+++ b/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023, 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 android.nearby.aidl;
+
+import android.nearby.NearbyDeviceParcelable;
+
+/**
+ * Listener for offload queries.
+ *
+ * {@hide}
+ */
+oneway interface IOffloadCallback {
+        /** Invokes when ContextHub transaction completes. */
+        void onQueryComplete(boolean isOffloadSupported);
+}
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index 2d0d327..10ace2c 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -20,6 +20,7 @@
     name: "HalfSheetUX",
     defaults: ["platform_app_defaults"],
     srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
     sdk_version: "module_current",
     // This is included in tethering apex, which uses min SDK 30
     min_sdk_version: "30",
@@ -28,7 +29,7 @@
     libs: [
         "framework-bluetooth",
         "framework-connectivity-t",
-        "nearby-service-string",
+        "framework-nearby-static",
     ],
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml b/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml
new file mode 100644
index 0000000..3dcfdee
--- /dev/null
+++ b/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M3,6h18L21,4L3,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h4v-2L3,18L3,6zM13,12L9,12v1.78c-0.61,0.55 -1,1.33 -1,2.22s0.39,1.67 1,2.22L9,20h4v-1.78c0.61,-0.55 1,-1.34 1,-2.22s-0.39,-1.67 -1,-2.22L13,12zM11,17.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM22,8h-6c-0.5,0 -1,0.5 -1,1v10c0,0.5 0.5,1 1,1h6c0.5,0 1,-0.5 1,-1L23,9c0,-0.5 -0.5,-1 -1,-1zM21,18h-4v-8h4v8z"/>
+</vector>
diff --git a/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml
new file mode 100644
index 0000000..545f7fa
--- /dev/null
+++ b/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    tools:ignore="RtlCompat"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="0dp"
+    android:layout_weight="1">
+
+  <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/image_view"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingTop="12dp"
+      android:paddingStart="12dp"
+      android:paddingEnd="12dp">
+
+    <TextView
+        android:id="@+id/header_subtitle"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:fontFamily="google-sans"
+        android:textSize="14sp"
+        android:gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <ImageView
+        android:id="@+id/pairing_pic"
+        android:layout_width="@dimen/fast_pair_half_sheet_land_image_size"
+        android:layout_height="@dimen/fast_pair_half_sheet_land_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <TextView
+        android:id="@+id/pin_code"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/fast_pair_half_sheet_land_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:visibility="invisible"
+        android:textSize="50sp"
+        android:letterSpacing="0.2"
+        android:fontFamily="google-sans-medium"
+        android:gravity="center"
+        android:importantForAccessibility="yes"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <ProgressBar
+        android:id="@+id/connect_progressbar"
+        android:layout_width="@dimen/fast_pair_half_sheet_land_image_size"
+        android:layout_height="4dp"
+        android:indeterminate="true"
+        android:indeterminateTint="@color/fast_pair_progress_color"
+        android:indeterminateTintMode="src_in"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_marginBottom="6dp"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:id="@+id/mid_part"
+        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+
+      <ImageView
+          android:id="@+id/info_icon"
+          android:layout_alignParentStart="true"
+          android:contentDescription="@null"
+          android:layout_centerInParent="true"
+          android:layout_marginEnd="10dp"
+          android:layout_toStartOf="@id/connect_btn"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          app:srcCompat="@drawable/fast_pair_ic_info"
+          android:visibility="invisible" />
+
+      <com.google.android.material.button.MaterialButton
+          android:id="@+id/connect_btn"
+          android:text="@string/paring_action_connect"
+          android:layout_height="wrap_content"
+          android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+          android:layout_centerInParent="true"
+          style="@style/HalfSheetButton" />
+
+    </RelativeLayout>
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settings_btn"
+        android:text="@string/paring_action_settings"
+        android:visibility="invisible"
+        android:layout_height="wrap_content"
+        android:layout_width="@dimen/fast_pair_half_sheet_land_image_size"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
+        style="@style/HalfSheetButton" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/cancel_btn"
+        android:text="@string/paring_action_done"
+        android:visibility="invisible"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/mid_part"
+        android:gravity="start|center_vertical"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        android:layout_marginBottom="10dp"
+        style="@style/HalfSheetButtonBorderless"/>
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/setup_btn"
+        android:text="@string/paring_action_launch"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/mid_part"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        android:layout_marginBottom="10dp"
+        android:visibility="invisible"
+        android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
+        android:layout_width="wrap_content"
+        style="@style/HalfSheetButton" />
+
+    <!--Empty place holder to prevent pairing button from being cut off by screen-->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="30dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/setup_btn"/>
+  </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
diff --git a/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml
new file mode 100644
index 0000000..e993536
--- /dev/null
+++ b/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="RtlCompat, UselessParent, MergeRootFrame"
+    android:id="@+id/background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+  <LinearLayout
+      android:id="@+id/card"
+      android:orientation="vertical"
+      android:transitionName="card"
+      android:layout_height="wrap_content"
+      android:layout_width="wrap_content"
+      android:layout_gravity= "center|bottom"
+      android:paddingLeft="12dp"
+      android:paddingRight="12dp"
+      android:background="@drawable/half_sheet_bg"
+      android:accessibilityLiveRegion="polite"
+      android:gravity="bottom">
+
+    <RelativeLayout
+        android:id="@+id/toolbar_wrapper"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="20dp"
+        android:paddingRight="20dp">
+
+      <ImageView
+          android:layout_marginTop="9dp"
+          android:layout_marginBottom="9dp"
+          android:id="@+id/toolbar_image"
+          android:layout_width="42dp"
+          android:layout_height="42dp"
+          android:contentDescription="@null"
+          android:layout_toStartOf="@id/toolbar_title"
+          android:layout_centerHorizontal="true"
+          android:visibility="invisible"/>
+
+      <TextView
+          android:layout_marginTop="18dp"
+          android:layout_marginBottom="18dp"
+          android:layout_centerHorizontal="true"
+          android:id="@+id/toolbar_title"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:fontFamily="google-sans-medium"
+          android:maxLines="1"
+          android:ellipsize="end"
+          android:textSize="24sp"
+          android:textColor="@color/fast_pair_half_sheet_text_color"
+          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
+    </RelativeLayout>
+
+    <FrameLayout
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+    />
+
+  </LinearLayout>
+
+</FrameLayout>
+
diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
index 7fbe229..77cd1ea 100644
--- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
+++ b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
@@ -5,7 +5,8 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:orientation="vertical"
     tools:ignore="RtlCompat"
-    android:layout_width="match_parent" android:layout_height="match_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
   <androidx.constraintlayout.widget.ConstraintLayout
       android:id="@+id/image_view"
@@ -16,6 +17,7 @@
       android:paddingEnd="12dp"
       android:paddingTop="12dp"
       android:paddingBottom="12dp">
+
     <TextView
         android:id="@+id/header_subtitle"
         android:textColor="@color/fast_pair_half_sheet_subtitle_color"
@@ -73,14 +75,15 @@
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:orientation="horizontal"
         app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent">
 
       <ImageView
           android:id="@+id/info_icon"
-          android:layout_width="24dp"
-          android:layout_height="24dp"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
           app:srcCompat="@drawable/fast_pair_ic_info"
           android:layout_centerInParent="true"
           android:contentDescription="@null"
@@ -113,12 +116,11 @@
         android:id="@+id/cancel_btn"
         android:text="@string/paring_action_done"
         android:visibility="invisible"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:gravity="start|center_vertical"
         android:layout_marginTop="6dp"
+        android:layout_marginBottom="16dp"
         style="@style/HalfSheetButtonBorderless"/>
 
     <com.google.android.material.button.MaterialButton
diff --git a/nearby/halfsheet/res/values-af/strings.xml b/nearby/halfsheet/res/values-af/strings.xml
index 7333e63..b0f5631 100644
--- a/nearby/halfsheet/res/values-af/strings.xml
+++ b/nearby/halfsheet/res/values-af/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Begin tans opstelling …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Stel toestel op"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Toestel is gekoppel"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Gekoppel aan “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kon nie koppel nie"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Kan nie koppel nie"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Probeer die toestel self saambind"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Probeer om die toestel in saambindmodus te sit"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Toestelle binne bereik"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Toestelle met jou rekening"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Jou gestoorde toestel is reg"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Naby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Toestelle"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Koppel tans …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Battery: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Klaar"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Stoor"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Koppel"</string>
diff --git a/nearby/halfsheet/res/values-am/strings.xml b/nearby/halfsheet/res/values-am/strings.xml
index da3b144..7c0aed4 100644
--- a/nearby/halfsheet/res/values-am/strings.xml
+++ b/nearby/halfsheet/res/values-am/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ማዋቀርን በመጀመር ላይ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"መሣሪያ አዋቅር"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"መሣሪያ ተገናኝቷል"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"ከ«%s» ጋር ተገናኝቷል"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"መገናኘት አልተቻለም"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"መገናኘት አልተቻለም"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"በእጅ ከመሣሪያው ጋር ለማጣመር ይሞክሩ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"መሣሪያውን ወደ ማጣመር ሁነታ ለማስገባት ይሞክሩ"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"በቅርብ ያሉ መሣሪያዎች"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ከመለያዎ ጋር መሣሪያዎች"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"የተቀመጠው መሣሪያዎ ይገኛል"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"በአቅራቢያ"</string>
+    <string name="common_devices" msgid="2635603125608104442">"መሣሪያዎች"</string>
+    <string name="common_connecting" msgid="160531481424245303">"በማገናኘት ላይ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ባትሪ፦ %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ተጠናቅቋል"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"አስቀምጥ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"አገናኝ"</string>
diff --git a/nearby/halfsheet/res/values-ar/strings.xml b/nearby/halfsheet/res/values-ar/strings.xml
index d0bfce4..089faaa 100644
--- a/nearby/halfsheet/res/values-ar/strings.xml
+++ b/nearby/halfsheet/res/values-ar/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"جارٍ الإعداد…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"إعداد جهاز"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"تمّ إقران الجهاز"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"‏تم الربط بـ \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"تعذّر الربط"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"يتعذَّر الاتصال"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"جرِّب الإقران يدويًا بالجهاز."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"جرِّب تشغيل الجهاز في وضع الإقران."</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"الأجهزة القريبة"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"الأجهزة المرتبطة بحسابك"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"جهازك المحفوظ متاح"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"مشاركة عن قرب"</string>
+    <string name="common_devices" msgid="2635603125608104442">"الأجهزة"</string>
+    <string name="common_connecting" msgid="160531481424245303">"جارٍ الاتصال…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"‏البطارية: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"تم"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"حفظ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ربط"</string>
diff --git a/nearby/halfsheet/res/values-as/strings.xml b/nearby/halfsheet/res/values-as/strings.xml
index 8ff4946..bb9dfcc 100644
--- a/nearby/halfsheet/res/values-as/strings.xml
+++ b/nearby/halfsheet/res/values-as/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ছেটআপ আৰম্ভ কৰি থকা হৈছে…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ডিভাইচ ছেট আপ কৰক"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ডিভাইচ সংযোগ কৰা হ’ল"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s”ৰ সৈতে সংযোগ কৰা হ’ল"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"সংযোগ কৰিব পৰা নগ’ল"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"সংযোগ কৰিব পৰা নাই"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ডিভাইচটোৰ সৈতে মেনুৱেলী পেয়াৰ কৰক"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ডিভাইচক পেয়াৰ কৰা ম’ডত ৰাখি চাওক"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"সংযোগ সীমাত থকা ডিভাইচসমূহ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"আপোনাৰ একাউণ্টত সংযোগ হোৱা ডিভাইচবোৰ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"আপুনি ছেভ কৰা ডিভাইচ উপলব্ধ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"নিকটৱৰ্তী"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ডিভাইচ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"সংযোগ কৰি থকা হৈছে…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"বেটাৰী: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"হ’ল"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ছেভ কৰক"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"সংযোগ কৰক"</string>
diff --git a/nearby/halfsheet/res/values-az/strings.xml b/nearby/halfsheet/res/values-az/strings.xml
index af499ef..844963b 100644
--- a/nearby/halfsheet/res/values-az/strings.xml
+++ b/nearby/halfsheet/res/values-az/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Ayarlama başladılır…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Cihazı quraşdırın"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Cihaz qoşulub"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” şəbəkəsinə qoşulub"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Qoşulmaq mümkün olmadı"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Qoşulmaq olmur"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Cihazı manual olaraq birləşdirin"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Cihazı qoşalaşdırma rejiminə qoymağa çalışın"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Əl altında olan cihazlar"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Hesabınızdakı cihazlar"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Yadda saxlanmış cihazınız əlçatandır"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Yaxınlıqda"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Cihazlar"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Qoşulur…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batareya: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Oldu"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Saxlayın"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Qoşun"</string>
diff --git a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml
index eea6b64..fcd1dc6 100644
--- a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml
+++ b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Podešavanje se pokreće…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Podesite uređaj"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Povezani ste sa uređajem %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspelo"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Povezivanje nije uspelo"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Probajte da uparite ručno sa uređajem"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Probajte da prebacite uređaj u režim uparivanja"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Uređaji u dometu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Uređaji povezani sa nalogom"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Sačuvani uređaj je dostupan"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"U blizini"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Uređaji"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Povezuje se…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterija: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Sačuvaj"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
diff --git a/nearby/halfsheet/res/values-be/strings.xml b/nearby/halfsheet/res/values-be/strings.xml
index a5c1ef6..f469922 100644
--- a/nearby/halfsheet/res/values-be/strings.xml
+++ b/nearby/halfsheet/res/values-be/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Пачынаецца наладжванне…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Наладзьце прыладу"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Прылада падключана"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Падключана да прылады \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не ўдалося падключыцца"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Не ўдалося падключыцца"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Паспрабуйце спалучыць прыладу ўручную"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Перавядзіце прыладу ў рэжым спалучэння"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Прылады ў межах дасягальнасці"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Прылады з вашым уліковым запісам"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Ёсць захаваная вамі прылада"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Паблізу"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Прылады"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Падключэнне…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Узровень зараду: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Гатова"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Захаваць"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Падключыць"</string>
diff --git a/nearby/halfsheet/res/values-bg/strings.xml b/nearby/halfsheet/res/values-bg/strings.xml
index 0ee7aef..a0c5103 100644
--- a/nearby/halfsheet/res/values-bg/strings.xml
+++ b/nearby/halfsheet/res/values-bg/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Настройването се стартира…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Настройване на устройството"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Устройството е свързано"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Установена е връзка с(ъс) „%s“"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Свързването не бе успешно"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Не може да се установи връзка"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Опитайте да сдвоите устройството ръчно"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Опитайте да зададете режим на сдвояване за устройството"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Устройства в обхват"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Устройства с профила ви"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Запазеното ви у-во е налице"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"В близост"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Устройства"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Свързва се…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батерия: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Запазване"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Свързване"</string>
diff --git a/nearby/halfsheet/res/values-bn/strings.xml b/nearby/halfsheet/res/values-bn/strings.xml
index 484e35b..272d285 100644
--- a/nearby/halfsheet/res/values-bn/strings.xml
+++ b/nearby/halfsheet/res/values-bn/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"সেট-আপ করা শুরু হচ্ছে…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ডিভাইস সেট-আপ করুন"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ডিভাইস কানেক্ট হয়েছে"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s”-এ কানেক্ট করা হয়েছে"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"কানেক্ট করা যায়নি"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"কানেক্ট করা যায়নি"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ডিভাইসে ম্যানুয়ালি পেয়ার করার চেষ্টা করুন"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ডিভাইস \'যোগ করার\' মোডে রাখার চেষ্টা করুন"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"কাছে রয়েছে এমন ডিভাইস"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"আপনার অ্যাকাউন্টের সাথে কানেক্ট থাকা ডিভাইস"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"আপনার সেভ করা ডিভাইস উপলভ্য আছে"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"আশেপাশে"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ডিভাইস"</string>
+    <string name="common_connecting" msgid="160531481424245303">"কানেক্ট করা হচ্ছে…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ব্যাটারি: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"হয়ে গেছে"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"সেভ করুন"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"কানেক্ট করুন"</string>
diff --git a/nearby/halfsheet/res/values-bs/strings.xml b/nearby/halfsheet/res/values-bs/strings.xml
index 2fc8644..47f13c3 100644
--- a/nearby/halfsheet/res/values-bs/strings.xml
+++ b/nearby/halfsheet/res/values-bs/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pokretanje postavljanja…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Postavi uređaj"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Povezano s uređajem “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspjelo"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nije moguće povezati"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Pokušajte ručno upariti uređaj"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Pokušajte staviti uređaj u način rada za uparivanje"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Uređaji u dometu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Uređaji s vašim računom"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Sačuvani uređaj je dostupan"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"U blizini"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Uređaji"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Povezivanje…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterija: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Sačuvaj"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
diff --git a/nearby/halfsheet/res/values-ca/strings.xml b/nearby/halfsheet/res/values-ca/strings.xml
index 8912792..44ebc3e 100644
--- a/nearby/halfsheet/res/values-ca/strings.xml
+++ b/nearby/halfsheet/res/values-ca/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciant la configuració…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura el dispositiu"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"El dispositiu s\'ha connectat"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connectat a %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"No s\'ha pogut connectar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"No es pot establir la connexió"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prova de vincular el dispositiu manualment"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prova d\'activar el mode de vinculació al dispositiu"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositius a l\'abast"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositius amb el teu compte"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispositiu desat disponible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"A prop"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositius"</string>
+    <string name="common_connecting" msgid="160531481424245303">"S\'està connectant…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Fet"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Desa"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connecta"</string>
diff --git a/nearby/halfsheet/res/values-cs/strings.xml b/nearby/halfsheet/res/values-cs/strings.xml
index 7e7ea3c..53d27ab 100644
--- a/nearby/halfsheet/res/values-cs/strings.xml
+++ b/nearby/halfsheet/res/values-cs/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Zahajování nastavení…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavení zařízení"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Zařízení je připojeno"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Připojeno k zařízení %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nelze se připojit"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nepodařilo se připojit"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Zkuste zařízení spárovat ručně"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Přepněte zařízení do režimu párování"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Zařízení v dosahu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Zařízení s vaším účtem"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Je dostupné uložené zařízení"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"V okolí"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Zařízení"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Připojování…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterie: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Hotovo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Uložit"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Připojit"</string>
diff --git a/nearby/halfsheet/res/values-da/strings.xml b/nearby/halfsheet/res/values-da/strings.xml
index 1d937e2..89b221f 100644
--- a/nearby/halfsheet/res/values-da/strings.xml
+++ b/nearby/halfsheet/res/values-da/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Begynder konfiguration…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurer enhed"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheden er forbundet"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Der er oprettet forbindelse til \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Forbindelsen kan ikke oprettes"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Kunne ikke forbindes"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prøv at parre med enheden manuelt"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prøv at sætte enheden i parringstilstand"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Enheder inden for rækkevidde"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Enheder med din konto"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Din gemte enhed er tilgængelig"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Tæt på"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Enheder"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Forbinder…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batteri: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Luk"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Gem"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Opret forbindelse"</string>
diff --git a/nearby/halfsheet/res/values-de/strings.xml b/nearby/halfsheet/res/values-de/strings.xml
index 9186a44..de54114 100644
--- a/nearby/halfsheet/res/values-de/strings.xml
+++ b/nearby/halfsheet/res/values-de/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Einrichtung wird gestartet..."</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Gerät einrichten"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Gerät verbunden"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Mit „%s“ verbunden"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Verbindung nicht möglich"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Kein Verbindungsaufbau möglich"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Versuche, das Gerät manuell zu koppeln"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Versuche, das Gerät in den Kopplungsmodus zu versetzen"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Geräte in Reichweite"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Geräte mit deinem Konto"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Gespeichertes Gerät verfügbar"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Geräte"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Wird verbunden…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Akkustand: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Fertig"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Speichern"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Verbinden"</string>
diff --git a/nearby/halfsheet/res/values-el/strings.xml b/nearby/halfsheet/res/values-el/strings.xml
index 3e18a93..1ea467a 100644
--- a/nearby/halfsheet/res/values-el/strings.xml
+++ b/nearby/halfsheet/res/values-el/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Έναρξη ρύθμισης…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Ρύθμιση συσκευής"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Η συσκευή συνδέθηκε"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Συνδέθηκε με τη συσκευή %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Αδυναμία σύνδεσης"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Δεν είναι δυνατή η σύνδεση"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Δοκιμάστε να κάνετε μη αυτόματη σύζευξη στη συσκευή"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Δοκιμάστε να θέσετε τη συσκευή σε λειτουργία σύζευξης"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Συσκευές εντός εμβέλειας"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Συσκευές με τον λογαριασμό σας"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Η αποθ. συσκ. είναι διαθέσιμη"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Κοντά"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Συσκευές"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Σύνδεση…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Μπαταρία: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Τέλος"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Αποθήκευση"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Σύνδεση"</string>
diff --git a/nearby/halfsheet/res/values-en-rAU/strings.xml b/nearby/halfsheet/res/values-en-rAU/strings.xml
index d4ed675..b7039a1 100644
--- a/nearby/halfsheet/res/values-en-rAU/strings.xml
+++ b/nearby/halfsheet/res/values-en-rAU/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connected to \'%s\'"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Unable to connect"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Try pairing to the device manually"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Try putting the device into pairing mode"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Devices within reach"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Devices with your account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Your saved device is available"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Devices"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connecting…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Battery: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
diff --git a/nearby/halfsheet/res/values-en-rCA/strings.xml b/nearby/halfsheet/res/values-en-rCA/strings.xml
index d4ed675..06b3a5e 100644
--- a/nearby/halfsheet/res/values-en-rCA/strings.xml
+++ b/nearby/halfsheet/res/values-en-rCA/strings.xml
@@ -17,10 +17,21 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
+    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting Setup…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connected to “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Unable to connect"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Try manually pairing to the device"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Try putting the device into pairing mode"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Devices within reach"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Devices with your account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Your saved device is available"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Devices"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connecting…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Battery: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
diff --git a/nearby/halfsheet/res/values-en-rGB/strings.xml b/nearby/halfsheet/res/values-en-rGB/strings.xml
index d4ed675..b7039a1 100644
--- a/nearby/halfsheet/res/values-en-rGB/strings.xml
+++ b/nearby/halfsheet/res/values-en-rGB/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connected to \'%s\'"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Unable to connect"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Try pairing to the device manually"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Try putting the device into pairing mode"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Devices within reach"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Devices with your account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Your saved device is available"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Devices"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connecting…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Battery: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
diff --git a/nearby/halfsheet/res/values-en-rIN/strings.xml b/nearby/halfsheet/res/values-en-rIN/strings.xml
index d4ed675..b7039a1 100644
--- a/nearby/halfsheet/res/values-en-rIN/strings.xml
+++ b/nearby/halfsheet/res/values-en-rIN/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connected to \'%s\'"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Unable to connect"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Try pairing to the device manually"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Try putting the device into pairing mode"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Devices within reach"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Devices with your account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Your saved device is available"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Devices"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connecting…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Battery: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
diff --git a/nearby/halfsheet/res/values-en-rXC/strings.xml b/nearby/halfsheet/res/values-en-rXC/strings.xml
index 460cc1b..c71272e 100644
--- a/nearby/halfsheet/res/values-en-rXC/strings.xml
+++ b/nearby/halfsheet/res/values-en-rXC/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎Starting Setup…‎‏‎‎‏‎"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎Set up device‎‏‎‎‏‎"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎Device connected‎‏‎‎‏‎"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎Connected to “%s”‎‏‎‎‏‎"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Couldn\'t connect‎‏‎‎‏‎"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎Unable to connect‎‏‎‎‏‎"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Try manually pairing to the device‎‏‎‎‏‎"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎Try putting the device into pairing mode‎‏‎‎‏‎"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎Devices within reach‎‏‎‎‏‎"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎Devices with your account‎‏‎‎‏‎"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎Your saved device is available‎‏‎‎‏‎"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎Nearby‎‏‎‎‏‎"</string>
+    <string name="common_devices" msgid="2635603125608104442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎Devices‎‏‎‎‏‎"</string>
+    <string name="common_connecting" msgid="160531481424245303">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎Connecting…‎‏‎‎‏‎"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎Battery: %d%%‎‏‎‎‏‎"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎Done‎‏‎‎‏‎"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎Save‎‏‎‎‏‎"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎Connect‎‏‎‎‏‎"</string>
diff --git a/nearby/halfsheet/res/values-es-rUS/strings.xml b/nearby/halfsheet/res/values-es-rUS/strings.xml
index d8fb283..05eb75d 100644
--- a/nearby/halfsheet/res/values-es-rUS/strings.xml
+++ b/nearby/halfsheet/res/values-es-rUS/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando la configuración…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configuración del dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Se conectó el dispositivo"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Se estableció conexión con \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"No se pudo establecer conexión"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"No se pudo establecer conexión"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Intenta vincular el dispositivo manualmente"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prueba poner el dispositivo en el modo de vinculación"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos al alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos con tu cuenta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"El dispositivo está disponible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Conectando…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batería: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Listo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
diff --git a/nearby/halfsheet/res/values-es/strings.xml b/nearby/halfsheet/res/values-es/strings.xml
index 4b8340a..7142a1a 100644
--- a/nearby/halfsheet/res/values-es/strings.xml
+++ b/nearby/halfsheet/res/values-es/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando configuración…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar el dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Conectado a \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"No se ha podido conectar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"No se ha podido conectar"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prueba a emparejar el dispositivo manualmente"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prueba a poner el dispositivo en modo Emparejamiento"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos al alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos conectados con tu cuenta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispositivo guardado disponible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Conectando…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batería: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Hecho"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
diff --git a/nearby/halfsheet/res/values-et/strings.xml b/nearby/halfsheet/res/values-et/strings.xml
index e6abc64..20a46a5 100644
--- a/nearby/halfsheet/res/values-et/strings.xml
+++ b/nearby/halfsheet/res/values-et/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Seadistuse käivitamine …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Seadistage seade"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Seade on ühendatud"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Ühendatud seadmega „%s“"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ühendamine ebaõnnestus"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Ühendust ei õnnestu luua"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Proovige seadmega käsitsi siduda"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Lülitage seade sidumisrežiimi"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Ühendusulatuses olevad seadmed"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Teie kontoga ühendatud seadmed"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Salvestatud seade on saadaval"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Läheduses"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Seadmed"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Ühendamine …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Aku: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Valmis"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Salvesta"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Ühenda"</string>
diff --git a/nearby/halfsheet/res/values-eu/strings.xml b/nearby/halfsheet/res/values-eu/strings.xml
index 4243fd5..cd6eb34 100644
--- a/nearby/halfsheet/res/values-eu/strings.xml
+++ b/nearby/halfsheet/res/values-eu/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Konfigurazio-prozesua abiarazten…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfiguratu gailua"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Konektatu da gailua"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"%s gailura konektatuta"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ezin izan da konektatu"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Ezin da konektatu"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Saiatu gailua eskuz parekatzen"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Jarri gailua parekatzeko moduan"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Estaldura-eremuko gailuak"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Kontura konektatutako gailuak"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Gordetako gailua erabilgarri dago"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Gailuak"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Konektatzen…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %% %d"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Eginda"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Gorde"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Konektatu"</string>
diff --git a/nearby/halfsheet/res/values-fa/strings.xml b/nearby/halfsheet/res/values-fa/strings.xml
index 3585f95..7490e0f 100644
--- a/nearby/halfsheet/res/values-fa/strings.xml
+++ b/nearby/halfsheet/res/values-fa/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"درحال شروع راه‌اندازی…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"راه‌اندازی دستگاه"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"دستگاه متصل شد"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"‏به «%s» متصل شد"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"متصل نشد"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"اتصال برقرار نشد"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"مرتبط‌سازی با دستگاه را به‌صورت دستی امتحان کنید"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"دستگاه را در حالت مرتبط‌سازی قرار دهید"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"دستگاه‌های دردسترس"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"دستگاه‌های متصل به حسابتان"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"دستگاه ذخیره‌شده‌تان دردسترس است"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"اطراف"</string>
+    <string name="common_devices" msgid="2635603125608104442">"دستگاه‌ها"</string>
+    <string name="common_connecting" msgid="160531481424245303">"درحال اتصال…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"‏باتری: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"تمام"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ذخیره"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"متصل کردن"</string>
diff --git a/nearby/halfsheet/res/values-fi/strings.xml b/nearby/halfsheet/res/values-fi/strings.xml
index e8d47de..b488b3e 100644
--- a/nearby/halfsheet/res/values-fi/strings.xml
+++ b/nearby/halfsheet/res/values-fi/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Aloitetaan käyttöönottoa…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Määritä laite"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Laite on yhdistetty"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"%s yhdistetty"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ei yhteyttä"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Yhdistäminen epäonnistui"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Yritä yhdistää laitteet manuaalisesti"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Kokeile asettaa laite parinmuodostustilaan"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Kantoalueella olevat laitteet"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Tiliisi liitetyt laitteet"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Laitteesi on käytettävissä"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Lähellä"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Laitteet"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Yhdistetään…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Akku: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Valmis"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Tallenna"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Yhdistä"</string>
diff --git a/nearby/halfsheet/res/values-fr-rCA/strings.xml b/nearby/halfsheet/res/values-fr-rCA/strings.xml
index 64dd107..9a48890 100644
--- a/nearby/halfsheet/res/values-fr-rCA/strings.xml
+++ b/nearby/halfsheet/res/values-fr-rCA/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Démarrage de la configuration…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurer l\'appareil"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Appareil associé"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connecté à l\'appareil suivant : « %s »"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossible d\'associer"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Échec de la connexion"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Essayez d\'associer manuellement l\'appareil"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Essayez de mettre l\'appareil en mode d\'association"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Appareils à portée"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Appareils connectés à votre compte"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Appareil enregistré accessible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"À proximité"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Appareils"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connexion en cours…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Pile : %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"OK"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Enregistrer"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Associer"</string>
diff --git a/nearby/halfsheet/res/values-fr/strings.xml b/nearby/halfsheet/res/values-fr/strings.xml
index 484c57b..f1263ab 100644
--- a/nearby/halfsheet/res/values-fr/strings.xml
+++ b/nearby/halfsheet/res/values-fr/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Début de la configuration…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurer un appareil"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Appareil associé"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connecté à \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossible de se connecter"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Impossible de se connecter"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Essayez d\'associer manuellement l\'appareil"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Essayez de mettre l\'appareil en mode association"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Appareils à portée de main"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Appareils connectés à votre compte"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Appareil enregistré disponible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"À proximité"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Appareils"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connexion…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batterie : %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"OK"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Enregistrer"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connecter"</string>
diff --git a/nearby/halfsheet/res/values-gl/strings.xml b/nearby/halfsheet/res/values-gl/strings.xml
index 30393ff..91eac4f 100644
--- a/nearby/halfsheet/res/values-gl/strings.xml
+++ b/nearby/halfsheet/res/values-gl/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando configuración…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura o dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Conectouse o dispositivo"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"O dispositivo conectouse a %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Non se puido conectar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Non se puido conectar"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Proba a vincular o dispositivo manualmente"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Proba a poñer o dispositivo no modo de vinculación"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos dentro do alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos conectados á túa conta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispositivo gardado dispoñible"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Conectando…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batería: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Feito"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Gardar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
diff --git a/nearby/halfsheet/res/values-gu/strings.xml b/nearby/halfsheet/res/values-gu/strings.xml
index 03b057d..a7a7a2b 100644
--- a/nearby/halfsheet/res/values-gu/strings.xml
+++ b/nearby/halfsheet/res/values-gu/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"સેટઅપ શરૂ કરી રહ્યાં છીએ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ડિવાઇસનું સેટઅપ કરો"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ડિવાઇસ કનેક્ટ કર્યું"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” સાથે કનેક્ટ કરેલું"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"કનેક્ટ કરી શક્યા નથી"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"કનેક્ટ કરી શકાયું નથી"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ડિવાઇસથી મેન્યૂઅલી જોડાણ બનાવવાનો પ્રયાસ કરો"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ડિવાઇસને જોડાણ બનાવવાના મોડમાં રાખવાનો પ્રયાસ કરો"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"પહોંચની અંદરના ડિવાઇસ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"તમારા એકાઉન્ટ સાથેના ડિવાઇસ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"તમારું સાચવેલું ડિવાઇસ ઉપલબ્ધ છે"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"શેરિંગ"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ડિવાઇસ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"કનેક્ટ કરી રહ્યાં છીએ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"બૅટરી: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"થઈ ગયું"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"સાચવો"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"કનેક્ટ કરો"</string>
diff --git a/nearby/halfsheet/res/values-hi/strings.xml b/nearby/halfsheet/res/values-hi/strings.xml
index ecd420e..dff9496 100644
--- a/nearby/halfsheet/res/values-hi/strings.xml
+++ b/nearby/halfsheet/res/values-hi/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेट अप शुरू किया जा रहा है…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिवाइस सेट अप करें"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिवाइस कनेक्ट हो गया"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” से कनेक्ट हो गया है"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट नहीं किया जा सका"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"कनेक्ट नहीं किया जा सका"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"डिवाइस को मैन्युअल तरीके से दूसरे डिवाइस से जोड़ने की कोशिश करें"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"डिवाइस को दूसरे डिवाइस से जोड़ने वाले मोड में रखें"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ऐसे डिवाइस जो रेंज में हैं"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"आपके खाते से जुड़े डिवाइस"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"सेव किया गया डिवाइस उपलब्ध है"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"आस-पास शेयरिंग"</string>
+    <string name="common_devices" msgid="2635603125608104442">"डिवाइस"</string>
+    <string name="common_connecting" msgid="160531481424245303">"कनेक्ट हो रहा है…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"बैटरी: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"हो गया"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"सेव करें"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट करें"</string>
diff --git a/nearby/halfsheet/res/values-hr/strings.xml b/nearby/halfsheet/res/values-hr/strings.xml
index 5a3de8f..13952b8 100644
--- a/nearby/halfsheet/res/values-hr/strings.xml
+++ b/nearby/halfsheet/res/values-hr/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pokretanje postavljanja…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Postavi uređaj"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Povezano s uređajem %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspjelo"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Povezivanje nije uspjelo"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Pokušajte ručno upariti s uređajem"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Pokušajte staviti uređaj u način uparivanja"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Uređaji u dometu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Uređaji s vašim računom"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Spremljeni je uređaj dostupan"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"U blizini"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Uređaji"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Povezivanje…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterija: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Spremi"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
diff --git a/nearby/halfsheet/res/values-hu/strings.xml b/nearby/halfsheet/res/values-hu/strings.xml
index ba3d2e0..3d810d4 100644
--- a/nearby/halfsheet/res/values-hu/strings.xml
+++ b/nearby/halfsheet/res/values-hu/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Beállítás megkezdése…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Eszköz beállítása"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Eszköz csatlakoztatva"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Csatlakoztatva a következőhöz: %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nem sikerült csatlakozni"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nem sikerült csatlakozni"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Próbálkozzon az eszköz kézi párosításával"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Próbálja meg bekapcsolni az eszközön a párosítási módot"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Elérhető eszközök"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"A fiókjával társított eszközök"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"A mentett eszköze elérhető"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Közeli"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Eszközök"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Csatlakozás…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Akkumulátor: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Kész"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Mentés"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Csatlakozás"</string>
diff --git a/nearby/halfsheet/res/values-hy/strings.xml b/nearby/halfsheet/res/values-hy/strings.xml
index ecabd16..57b9256 100644
--- a/nearby/halfsheet/res/values-hy/strings.xml
+++ b/nearby/halfsheet/res/values-hy/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Կարգավորում…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Կարգավորեք սարքը"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Սարքը զուգակցվեց"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"«%s» սարքի հետ կապը հաստատվեց"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Չհաջողվեց միանալ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Չհաջողվեց կապ հաստատել"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Փորձեք ձեռքով զուգակցել սարքը"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Փորձեք սարքում միացնել զուգակցման ռեժիմը"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Սարքեր հասանելիության տիրույթում"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Ձեր հաշվի հետ կապված սարքեր"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Պահված սարքը հասանելի է"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Մոտակայքում"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Սարքեր"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Միացում…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Մարտկոցի լիցքը՝ %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Պատրաստ է"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Պահել"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Միանալ"</string>
diff --git a/nearby/halfsheet/res/values-in/strings.xml b/nearby/halfsheet/res/values-in/strings.xml
index dc777b2..c665572 100644
--- a/nearby/halfsheet/res/values-in/strings.xml
+++ b/nearby/halfsheet/res/values-in/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Memulai Penyiapan …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Siapkan perangkat"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Perangkat terhubung"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Terhubung ke “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tidak dapat terhubung"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Tidak dapat terhubung"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Coba sambungkan ke perangkat secara manual"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Coba masukkan perangkat ke dalam mode penyambungan"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Perangkat dalam jangkauan"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Perangkat dengan akun Anda"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Perangkat tersimpan tersedia"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Berbagi Langsung"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Perangkat"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Menghubungkan …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Daya baterai: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Selesai"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Simpan"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Hubungkan"</string>
diff --git a/nearby/halfsheet/res/values-is/strings.xml b/nearby/halfsheet/res/values-is/strings.xml
index ee094d9..04a4de4 100644
--- a/nearby/halfsheet/res/values-is/strings.xml
+++ b/nearby/halfsheet/res/values-is/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Ræsir uppsetningu…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Uppsetning tækis"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Tækið er tengt"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Tengt við „%s“"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tenging mistókst"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Tenging tókst ekki"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prófaðu að para tækið handvirkt"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prófaðu að kveikja á pörunarstillingu í tækinu"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Nálæg tæki"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Tæki tengd reikningnum þínum"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Vistaða tækið er tiltækt"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nálægt"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Tæki"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Tengist…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Rafhlaða: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Lokið"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Vista"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Tengja"</string>
diff --git a/nearby/halfsheet/res/values-it/strings.xml b/nearby/halfsheet/res/values-it/strings.xml
index 700dd77..2ffe268 100644
--- a/nearby/halfsheet/res/values-it/strings.xml
+++ b/nearby/halfsheet/res/values-it/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Avvio della configurazione…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo connesso"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Connesso a \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossibile connettere"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Impossibile connettersi"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prova a eseguire l\'accoppiamento manuale con il dispositivo"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prova a impostare il dispositivo sulla modalità di accoppiamento"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivi nelle vicinanze"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivi collegati all\'account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Disposit. salvato disponibile"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nelle vicinanze"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivi"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Connessione in corso"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batteria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Fine"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Salva"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Connetti"</string>
diff --git a/nearby/halfsheet/res/values-iw/strings.xml b/nearby/halfsheet/res/values-iw/strings.xml
index e6ff9b9..61724f0 100644
--- a/nearby/halfsheet/res/values-iw/strings.xml
+++ b/nearby/halfsheet/res/values-iw/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ההגדרה מתבצעת…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"הגדרת המכשיר"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"המכשיר מחובר"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"‏יש חיבור אל \'%s\'"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"לא ניתן להתחבר"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"לא ניתן להתחבר"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"כדאי לנסות לבצע התאמה ידנית למכשיר"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"כדאי לנסות להעביר את המכשיר למצב התאמה"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"מכשירים בהישג יד"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"מכשירים המחוברים לחשבון שלך"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"המכשיר ששמרת זמין"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"בקרבת מקום"</string>
+    <string name="common_devices" msgid="2635603125608104442">"מכשירים"</string>
+    <string name="common_connecting" msgid="160531481424245303">"מתבצעת התחברות…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"‏טעינת הסוללה: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"סיום"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"שמירה"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"התחברות"</string>
diff --git a/nearby/halfsheet/res/values-ja/strings.xml b/nearby/halfsheet/res/values-ja/strings.xml
index a429b7e..3168bda 100644
--- a/nearby/halfsheet/res/values-ja/strings.xml
+++ b/nearby/halfsheet/res/values-ja/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"セットアップを開始中…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"デバイスのセットアップ"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"デバイス接続完了"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"「%s」に接続しました"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"接続エラー"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"接続できません"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"手動でデバイスとペア設定してみてください"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"デバイスをペア設定モードにしてください"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"近接するデバイス"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"アカウントと接続済みのデバイス"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"保存済みのデバイスがあります"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"周辺ユーザーとの共有"</string>
+    <string name="common_devices" msgid="2635603125608104442">"デバイス"</string>
+    <string name="common_connecting" msgid="160531481424245303">"接続しています…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"バッテリー: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"完了"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"保存"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"接続"</string>
diff --git a/nearby/halfsheet/res/values-ka/strings.xml b/nearby/halfsheet/res/values-ka/strings.xml
index 4353ae9..a9ee648 100644
--- a/nearby/halfsheet/res/values-ka/strings.xml
+++ b/nearby/halfsheet/res/values-ka/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"დაყენება იწყება…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"მოწყობილობის დაყენება"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"მოწყობილობა დაკავშირებულია"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"დაკავშირებულია „%s“-თან"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"დაკავშირება ვერ მოხერხდა"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"დაკავშირება ვერ ხერხდება"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ცადეთ მოწყობილობასთან ხელით დაწყვილება"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ცადეთ მოწყობილობის გადაყვანა დაწყვილების რეჟიმზე"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ხელმისაწვდომი მოწყობილობები"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"მოწყობილობები თქვენი ანგარიშით"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"შენახული მოწყობილობა ხელმისაწვდომია"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ახლომახლო"</string>
+    <string name="common_devices" msgid="2635603125608104442">"მოწყობილობები"</string>
+    <string name="common_connecting" msgid="160531481424245303">"მიმდინარეობს დაკავშირება…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ბატარეა: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"მზადაა"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"შენახვა"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"დაკავშირება"</string>
diff --git a/nearby/halfsheet/res/values-kk/strings.xml b/nearby/halfsheet/res/values-kk/strings.xml
index 98d8073..6e1a0bd 100644
--- a/nearby/halfsheet/res/values-kk/strings.xml
+++ b/nearby/halfsheet/res/values-kk/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Реттеу басталуда…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Құрылғыны реттеу"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Құрылғы байланыстырылды"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"\"%s\" құрылғысымен байланыстырылды"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Қосылмады"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Қосылу мүмкін емес"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Құрылғыны қолмен жұптап көріңіз."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Құрылғыны жұптау режиміне қойып көріңіз."</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Қолжетімді құрылғылар"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Аккаунтпен байланыстырылған құрылғылар"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Сақталған құрылғы қолжетімді"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Маңайдағы"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Құрылғылар"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Жалғанып жатыр…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батарея: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Дайын"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Сақтау"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Қосу"</string>
diff --git a/nearby/halfsheet/res/values-km/strings.xml b/nearby/halfsheet/res/values-km/strings.xml
index 85e39db..deb6504 100644
--- a/nearby/halfsheet/res/values-km/strings.xml
+++ b/nearby/halfsheet/res/values-km/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"កំពុងចាប់ផ្ដើម​រៀបចំ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"រៀបចំ​ឧបករណ៍"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"បានភ្ជាប់ឧបករណ៍"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"បានភ្ជាប់ជាមួយ “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"មិន​អាចភ្ជាប់​បានទេ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"មិនអាច​ភ្ជាប់​បានទេ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"សាកល្បង​ផ្គូផ្គង​ដោយ​ផ្ទាល់​ទៅឧបករណ៍"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"សាកល្បង​កំណត់​ឧបករណ៍​ឱ្យ​ប្រើ​មុខងារ​​ផ្គូផ្គង"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ឧបករណ៍​នៅ​ក្បែរដៃ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ឧបករណ៍​ភ្ជាប់​ជាមួយ​គណនី​របស់អ្នក​"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ឧបករណ៍ដែលអ្នកបានរក្សាទុកអាចប្រើបានហើយ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"នៅ​ជិត"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ឧបករណ៍"</string>
+    <string name="common_connecting" msgid="160531481424245303">"កំពុងភ្ជាប់…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ថ្ម៖ %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"រួចរាល់"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"រក្សាទុក"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ភ្ជាប់"</string>
diff --git a/nearby/halfsheet/res/values-kn/strings.xml b/nearby/halfsheet/res/values-kn/strings.xml
index fb62bb1..87b4fe3 100644
--- a/nearby/halfsheet/res/values-kn/strings.xml
+++ b/nearby/halfsheet/res/values-kn/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ಸೆಟಪ್ ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ಸಾಧನವನ್ನು ಸೆಟಪ್ ಮಾಡಿ"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ಸಾಧನಕ್ಕೆ ಹಸ್ತಚಾಲಿತವಾಗಿ ಜೋಡಿಸಲು ಪ್ರಯತ್ನಿಸಿ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ಜೋಡಿಸುವಿಕೆ ಮೋಡ್‌ನಲ್ಲಿ ಸಾಧನವನ್ನು ಇರಿಸಲು ಪ್ರಯತ್ನಿಸಿ"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ವ್ಯಾಪ್ತಿಯಲ್ಲಿರುವ ಸಾಧನಗಳು"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿತವಾಗಿರುವ ಸಾಧನಗಳು"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸಾಧನವು ಲಭ್ಯವಿದೆ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ಸಮೀಪದಲ್ಲಿರುವುದು"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ಸಾಧನಗಳು"</string>
+    <string name="common_connecting" msgid="160531481424245303">"ಕನೆಕ್ಟ್ ಆಗುತ್ತಿದೆ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ಬ್ಯಾಟರಿ: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ಮುಗಿದಿದೆ"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ಉಳಿಸಿ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
diff --git a/nearby/halfsheet/res/values-ko/strings.xml b/nearby/halfsheet/res/values-ko/strings.xml
index c94ff76..37d50da 100644
--- a/nearby/halfsheet/res/values-ko/strings.xml
+++ b/nearby/halfsheet/res/values-ko/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"설정을 시작하는 중…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"기기 설정"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"기기 연결됨"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"\'%s\'에 연결됨"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"연결할 수 없음"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"연결할 수 없음"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"기기에 수동으로 페어링을 시도해 보세요."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"기기를 페어링 모드로 전환하세요."</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"연결 가능 기기"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"내 계정에 연결된 기기"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"저장된 기기 사용 가능"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nearby"</string>
+    <string name="common_devices" msgid="2635603125608104442">"기기"</string>
+    <string name="common_connecting" msgid="160531481424245303">"연결 중…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"배터리: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"완료"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"저장"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"연결"</string>
diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml
index 812e0e8..b6aa409 100644
--- a/nearby/halfsheet/res/values-ky/strings.xml
+++ b/nearby/halfsheet/res/values-ky/strings.xml
@@ -20,10 +20,21 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Жөндөлүп баштады…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Түзмөктү жөндөө"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Түзмөк туташты"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” түзмөгүнө туташты"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Туташпай койду"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Туташпай жатат"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Түзмөккө кол менен жупташтырып көрүңүз"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Түзмөктү Байланыштыруу режимине коюп көрүңүз"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Жеткиликтүү түзмөктөр"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Аккаунтуңузга кирип турган түзмөктөр"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Сакталган түзмөгүңүз жеткиликтүү"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Жакын жерде"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Түзмөктөр"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Туташууда…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батарея: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Бүттү"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Сактоо"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Туташуу"</string>
     <string name="paring_action_launch" msgid="8940808384126591230">"Жөндөө"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Жөндөөлөр"</string>
+    <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
 </resources>
diff --git a/nearby/halfsheet/res/values-lo/strings.xml b/nearby/halfsheet/res/values-lo/strings.xml
index 9c945b2..cbc7601 100644
--- a/nearby/halfsheet/res/values-lo/strings.xml
+++ b/nearby/halfsheet/res/values-lo/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ກຳລັງເລີ່ມການຕັ້ງຄ່າ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ຕັ້ງຄ່າອຸປະກອນ"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ເຊື່ອມຕໍ່ອຸປະກອນແລ້ວ"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"ເຊື່ອມຕໍ່ກັບ “%s” ແລ້ວ"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ລອງຈັບຄູ່ອຸປະກອນດ້ວຍຕົນເອງ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ໃຫ້ລອງຕັ້ງອຸປະກອນເປັນໂໝດຈັບຄູ່"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ອຸປະກອນພ້ອມໃຫ້ເຂົ້າເຖິງ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ອຸປະກອນທີ່ມີບັນຊີຂອງທ່ານ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ອຸປະກອນທີ່ບັນທຶກໄວ້ຂອງທ່ານສາມາດໃຊ້ໄດ້"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ໃກ້ຄຽງ"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ອຸປະກອນ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"ກຳລັງເຊື່ອມຕໍ່…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ແບັດເຕີຣີ: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ແລ້ວໆ"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ບັນທຶກ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ເຊື່ອມຕໍ່"</string>
diff --git a/nearby/halfsheet/res/values-lt/strings.xml b/nearby/halfsheet/res/values-lt/strings.xml
index 5dbad0a..29a9bc5 100644
--- a/nearby/halfsheet/res/values-lt/strings.xml
+++ b/nearby/halfsheet/res/values-lt/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pradedama sąranka…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Įrenginio nustatymas"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Įrenginys prijungtas"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Prisijungta prie „%s“"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Prisijungti nepavyko"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nepavyko prisijungti"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Pabandykite neautomatiškai susieti įrenginį"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Pabandykite vėl įgalinti įrenginio susiejimo režimą"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Lengvai pasiekiami įrenginiai"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Įrenginiai su jūsų paskyra"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Išsaugotas įrenginys pasiekiamas"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Netoliese"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Įrenginiai"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Prisijungiama…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Akumuliatorius: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Atlikta"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Išsaugoti"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Prisijungti"</string>
diff --git a/nearby/halfsheet/res/values-lv/strings.xml b/nearby/halfsheet/res/values-lv/strings.xml
index a9e1bf9..9573357 100644
--- a/nearby/halfsheet/res/values-lv/strings.xml
+++ b/nearby/halfsheet/res/values-lv/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Tiek sākta iestatīšana…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Iestatiet ierīci"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Ierīce ir pievienota"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Izveidots savienojums ar ierīci “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nevarēja izveidot savienojumu"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nevar izveidot savienojumu."</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Mēģiniet manuāli izveidot savienojumu pārī ar ierīci."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Ieslēdziet ierīcē režīmu savienošanai pārī"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Sasniedzamas ierīces"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Ierīces ar jūsu kontu"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Jūsu saglabātā ierīce pieejama"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Tuvumā"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Ierīces"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Savienojuma izveide…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Akumulatora uzlādes līmenis: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gatavs"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Saglabāt"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Izveidot savienojumu"</string>
diff --git a/nearby/halfsheet/res/values-mk/strings.xml b/nearby/halfsheet/res/values-mk/strings.xml
index e29dfa1..693f112 100644
--- a/nearby/halfsheet/res/values-mk/strings.xml
+++ b/nearby/halfsheet/res/values-mk/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Се започнува со поставување…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Поставете го уредот"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Уредот е поврзан"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Поврзан со „%s“"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не може да се поврзе"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Не може да се поврзе"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Обидете се рачно да се спарите со уредот"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Пробајте да го ставите уредот во режим на спарување"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Уреди на дофат"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Уреди поврзани со вашата сметка"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Вашиот зачуван уред е достапен"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Во близина"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Уреди"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Се поврзува…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батерија: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Зачувај"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Поврзи"</string>
diff --git a/nearby/halfsheet/res/values-ml/strings.xml b/nearby/halfsheet/res/values-ml/strings.xml
index cbc171b..56a2db2 100644
--- a/nearby/halfsheet/res/values-ml/strings.xml
+++ b/nearby/halfsheet/res/values-ml/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"സജ്ജീകരിക്കൽ ആരംഭിക്കുന്നു…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ഉപകരണം സജ്ജീകരിക്കുക"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ഉപകരണം കണക്റ്റ് ചെയ്‌തു"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” എന്നതിലേക്ക് കണക്റ്റ് ചെയ്‌തു"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"കണക്റ്റ് ചെയ്യാനായില്ല"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"കണക്റ്റ് ചെയ്യാനാകുന്നില്ല"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ഉപകരണം ജോടിയാക്കാൻ നേരിട്ട് ശ്രമിക്കുക"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ഉപകരണം ജോഡിയാക്കൽ മോഡിലേക്ക് മാറ്റിയ ശേഷം ശ്രമിക്കുക"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"പരിധിയിലുള്ള ഉപകരണങ്ങൾ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"അക്കൗണ്ട് ഉപയോഗിക്കുന്ന ഉപകരണങ്ങൾ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"സംരക്ഷിച്ച ഉപകരണം ലഭ്യമാണ്"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"സമീപമുള്ളവ"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ഉപകരണങ്ങൾ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"കണക്റ്റ് ചെയ്യുന്നു…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ബാറ്ററി: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"പൂർത്തിയായി"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"സംരക്ഷിക്കുക"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"കണക്റ്റ് ചെയ്യുക"</string>
diff --git a/nearby/halfsheet/res/values-mn/strings.xml b/nearby/halfsheet/res/values-mn/strings.xml
index 6d21eff..5a72ce3 100644
--- a/nearby/halfsheet/res/values-mn/strings.xml
+++ b/nearby/halfsheet/res/values-mn/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Тохируулгыг эхлүүлж байна…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Төхөөрөмж тохируулах"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Төхөөрөмж холбогдсон"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s”-д холбогдсон"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Холбогдож чадсангүй"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Холбогдох боломжгүй байна"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Төхөөрөмжийг гар аргаар хослуулна уу"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Төхөөрөмжийг хослуулах горимд оруулахаар оролдоно уу"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Хүрээн дэх төхөөрөмжүүд"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Таны бүртгэлтэй холбогдсон төхөөрөмж"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Таны хадгалсан төхөөрөмж бэлэн байна"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Ойролцоо"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Төхөөрөмж"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Холбогдож байна…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батарей: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Болсон"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Хадгалах"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Холбох"</string>
diff --git a/nearby/halfsheet/res/values-mr/strings.xml b/nearby/halfsheet/res/values-mr/strings.xml
index a3e1d7a..3eeb0ec 100644
--- a/nearby/halfsheet/res/values-mr/strings.xml
+++ b/nearby/halfsheet/res/values-mr/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेटअप सुरू करत आहे…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिव्हाइस सेट करा"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिव्हाइस कनेक्ट केले आहे"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"\"%s\" शी कनेक्ट केले आहे"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट करता आले नाही"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"कनेक्ट करता आले नाही"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"डिव्हाइसशी मॅन्युअली पेअर करण्याचा प्रयत्न करा"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"डिव्हाइसला पेअरिंग मोडमध्ये ठेवण्याचा प्रयत्न करा"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"पुरेसे जवळ असलेले डिव्हाइस"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"तुमच्या खात्‍यासह असलेली डिव्‍हाइस"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"सेव्ह केलेले डिव्हाइस उपलब्ध"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"जवळपास"</string>
+    <string name="common_devices" msgid="2635603125608104442">"डिव्हाइस"</string>
+    <string name="common_connecting" msgid="160531481424245303">"कनेक्ट करत आहे…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"बॅटरी: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"पूर्ण झाले"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"सेव्ह करा"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट करा"</string>
diff --git a/nearby/halfsheet/res/values-ms/strings.xml b/nearby/halfsheet/res/values-ms/strings.xml
index 4835c1b..0af903d 100644
--- a/nearby/halfsheet/res/values-ms/strings.xml
+++ b/nearby/halfsheet/res/values-ms/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Memulakan Persediaan…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Sediakan peranti"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Peranti disambungkan"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Disambungkan kepada “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tidak dapat menyambung"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Tidak dapat menyambung"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Cuba gandingkan dengan peranti secara manual"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Cuba letakkan peranti dalam mod penggandingan"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Peranti dalam capaian"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Peranti dengan akaun anda"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Peranti disimpan anda tersedia"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Berdekatan"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Peranti"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Menyambung…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateri: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Selesai"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Simpan"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Sambung"</string>
diff --git a/nearby/halfsheet/res/values-my/strings.xml b/nearby/halfsheet/res/values-my/strings.xml
index 32c3105..306538f 100644
--- a/nearby/halfsheet/res/values-my/strings.xml
+++ b/nearby/halfsheet/res/values-my/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"စနစ်ထည့်သွင်းခြင်း စတင်နေသည်…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"စက်ကို စနစ်ထည့်သွင်းရန်"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"စက်ကို ချိတ်ဆက်လိုက်ပြီ"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” သို့ ချိတ်ဆက်ထားသည်"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"ချိတ်ဆက်၍မရပါ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"ချိတ်ဆက်၍ မရပါ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"စက်ကို ကိုယ်တိုင်တွဲချိတ်ကြည့်ပါ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"စက်ကို တွဲချိတ်ခြင်းမုဒ်သို့ ထားကြည့်ပါ"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"လက်လှမ်းမီသည့် စက်များ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"သင့်အကောင့်ရှိသည့် စက်များ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"သင်သိမ်းထားသောစက် ရပါပြီ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"အနီးတစ်ဝိုက်"</string>
+    <string name="common_devices" msgid="2635603125608104442">"စက်များ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"ချိတ်ဆက်နေသည်…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ဘက်ထရီ− %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ပြီးပြီ"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"သိမ်းရန်"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ချိတ်ဆက်ရန်"</string>
diff --git a/nearby/halfsheet/res/values-nb/strings.xml b/nearby/halfsheet/res/values-nb/strings.xml
index 9d72565..72a2ab7 100644
--- a/nearby/halfsheet/res/values-nb/strings.xml
+++ b/nearby/halfsheet/res/values-nb/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starter konfigureringen …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurer enheten"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheten er tilkoblet"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Koblet til «%s»"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kunne ikke koble til"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Kan ikke koble til"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Prøv manuell tilkobling til enheten"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prøv å sette enheten i tilkoblingsmodus"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Enheter innen rekkevidde"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Enheter med kontoen din"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Lagret enhet tilgjengelig"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"I nærheten"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Enheter"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Kobler til …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batteri: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Ferdig"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Lagre"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Koble til"</string>
diff --git a/nearby/halfsheet/res/values-ne/strings.xml b/nearby/halfsheet/res/values-ne/strings.xml
index 1370412..2956768 100644
--- a/nearby/halfsheet/res/values-ne/strings.xml
+++ b/nearby/halfsheet/res/values-ne/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेटअप प्रक्रिया सुरु गरिँदै छ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिभाइस सेटअप गर्नुहोस्"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिभाइस कनेक्ट गरियो"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"\"%s\" मा कनेक्ट गरिएको छ"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट गर्न सकिएन"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"कनेक्ट गर्न सकिएन"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"म्यानुअल तरिकाले डिभाइस कनेक्ट गरी हेर्नुहोस्"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"डिभाइस कनेक्ट गर्ने मोडमा राखी हेर्नुहोस्"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"कनेक्ट गर्न सकिने नजिकैका डिभाइसहरू"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"तपाईंको खातामा कनेक्ट गरिएका डिभाइस"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"तपाईंले सेभ गरेको डिभाइस उपलब्ध छ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"नजिकै"</string>
+    <string name="common_devices" msgid="2635603125608104442">"डिभाइसहरू"</string>
+    <string name="common_connecting" msgid="160531481424245303">"कनेक्ट गरिँदै छ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ब्याट्री: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"सम्पन्न भयो"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"सेभ गर्नुहोस्"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट गर्नुहोस्"</string>
diff --git a/nearby/halfsheet/res/values-night/colors.xml b/nearby/halfsheet/res/values-night/colors.xml
new file mode 100644
index 0000000..69b832a
--- /dev/null
+++ b/nearby/halfsheet/res/values-night/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    <!-- Use original background color -->
+    <color name="fast_pair_notification_background">#00000000</color>
+    <!-- Ignores NewApi as below system colors are available since API 31, and HalfSheet is always
+         running on T+ even though it has min_sdk 30 to match its containing APEX -->
+    <color name="fast_pair_half_sheet_button_color" tools:ignore="NewApi">
+        @android:color/system_accent1_100</color>
+    <color name="fast_pair_half_sheet_button_text" tools:ignore="NewApi">
+        @android:color/system_neutral1_50</color>
+    <color name="fast_pair_half_sheet_button_accent_text" tools:ignore="NewApi">
+        @android:color/system_neutral1_900</color>
+    <color name="fast_pair_progress_color" tools:ignore="NewApi">
+        @android:color/system_accent1_600</color>
+    <color name="fast_pair_half_sheet_subtitle_color" tools:ignore="NewApi">
+        @android:color/system_neutral2_200</color>
+    <color name="fast_pair_half_sheet_text_color" tools:ignore="NewApi">
+        @android:color/system_neutral1_50</color>
+    <color name="fast_pair_half_sheet_background" tools:ignore="NewApi">
+        @android:color/system_neutral1_800</color>
+
+    <!-- Fast Pair -->
+    <color name="fast_pair_primary_text">#FFFFFF</color>
+    <color name="fast_pair_notification_image_outline">#24FFFFFF</color>
+    <color name="fast_pair_battery_level_low">#F6AEA9</color>
+
+</resources>
+
diff --git a/nearby/halfsheet/res/values-nl/strings.xml b/nearby/halfsheet/res/values-nl/strings.xml
index 4eb7624..e956116 100644
--- a/nearby/halfsheet/res/values-nl/strings.xml
+++ b/nearby/halfsheet/res/values-nl/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Instellen starten…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Apparaat instellen"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Apparaat verbonden"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Verbonden met %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kan geen verbinding maken"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Kan geen verbinding maken"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Probeer handmatig met het apparaat te koppelen"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Activeer de koppelingsstand van het apparaat"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Apparaten binnen bereik"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Apparaten met je account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Opgeslagen apparaat beschikbaar"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"In de buurt"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Apparaten"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Verbinden…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batterij: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Klaar"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Opslaan"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Verbinden"</string>
diff --git a/nearby/halfsheet/res/values-or/strings.xml b/nearby/halfsheet/res/values-or/strings.xml
index c5e8cfc..0ec472c 100644
--- a/nearby/halfsheet/res/values-or/strings.xml
+++ b/nearby/halfsheet/res/values-or/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ସେଟଅପ ଆରମ୍ଭ କରାଯାଉଛି…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ଡିଭାଇସ ସେଟ ଅପ କରନ୍ତୁ"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ଡିଭାଇସ ସଂଯୁକ୍ତ ହୋଇଛି"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"କନେକ୍ଟ କରିବାରେ ଅସମର୍ଥ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ଡିଭାଇସ ସହ ମାନୁଆଲୀ ପେୟାର କରିବା ପାଇଁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ଡିଭାଇସକୁ ପେୟାରିଂ ମୋଡରେ ରଖିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ପହଞ୍ଚ ଭିତରେ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ସହ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ସେଭ ଥିବା ଆପଣଙ୍କ ଡିଭାଇସ ଉପଲବ୍ଧ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ଆଖପାଖର"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ଡିଭାଇସଗୁଡ଼ିକ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"କନେକ୍ଟ କରାଯାଉଛି…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ବେଟେରୀ: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ହୋଇଗଲା"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ସେଭ କରନ୍ତୁ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ସଂଯୋଗ କରନ୍ତୁ"</string>
diff --git a/nearby/halfsheet/res/values-pa/strings.xml b/nearby/halfsheet/res/values-pa/strings.xml
index f0523a3..4eb0553 100644
--- a/nearby/halfsheet/res/values-pa/strings.xml
+++ b/nearby/halfsheet/res/values-pa/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ਸੈੱਟਅੱਪ ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ਡੀਵਾਈਸ ਸੈੱਟਅੱਪ ਕਰੋ"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” ਨਾਲ ਕਨੈਕਟ ਹੈ"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ਡੀਵਾਈਸ ਨਾਲ ਹੱਥੀਂ ਜੋੜਾਬੱਧ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"ਡੀਵਾਈਸ ਨੂੰ ਜੋੜਾਬੱਧਕਰਨ ਮੋਡ ਵਿੱਚ ਰੱਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ਡੀਵਾਈਸ ਜੋ ਪਹੁੰਚ ਅੰਦਰ ਹਨ"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ਤੁਹਾਡੇ ਖਾਤੇ ਵਾਲੇ ਡੀਵਾਈਸ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ਰੱਖਿਅਤ ਕੀਤਾ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੈ"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ਨਜ਼ਦੀਕੀ"</string>
+    <string name="common_devices" msgid="2635603125608104442">"ਡੀਵਾਈਸ"</string>
+    <string name="common_connecting" msgid="160531481424245303">"ਕਨੈਕਟ ਹੋ ਰਿਹਾ ਹੈ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"ਬੈਟਰੀ: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ਹੋ ਗਿਆ"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"ਰੱਖਿਅਤ ਕਰੋ"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"ਕਨੈਕਟ ਕਰੋ"</string>
diff --git a/nearby/halfsheet/res/values-pl/strings.xml b/nearby/halfsheet/res/values-pl/strings.xml
index 5abf5fd..5082e18 100644
--- a/nearby/halfsheet/res/values-pl/strings.xml
+++ b/nearby/halfsheet/res/values-pl/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Rozpoczynam konfigurowanie…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Skonfiguruj urządzenie"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Urządzenie połączone"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Połączono z: „%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nie udało się połączyć"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nie udało się połączyć"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Spróbuj ręcznie sparować urządzenie"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Włącz na urządzeniu tryb parowania"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Urządzenia w zasięgu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Urządzenia z Twoim kontem"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Zapisane urządzenie dostępne"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"W pobliżu"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Urządzenia"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Łączę…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gotowe"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Zapisz"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Połącz"</string>
diff --git a/nearby/halfsheet/res/values-pt-rBR/strings.xml b/nearby/halfsheet/res/values-pt-rBR/strings.xml
index b021b39..15d29d2 100644
--- a/nearby/halfsheet/res/values-pt-rBR/strings.xml
+++ b/nearby/halfsheet/res/values-pt-rBR/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando a configuração…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Conectado ao dispositivo \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Erro ao conectar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Não foi possível conectar"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Tente parear o dispositivo manualmente"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Coloque o dispositivo no modo de pareamento"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos ao alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos conectados à sua conta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispositivo salvo disponível"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Por perto"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Conectando…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Concluído"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Salvar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
diff --git a/nearby/halfsheet/res/values-pt-rPT/strings.xml b/nearby/halfsheet/res/values-pt-rPT/strings.xml
index 3285c73..ab8decf 100644
--- a/nearby/halfsheet/res/values-pt-rPT/strings.xml
+++ b/nearby/halfsheet/res/values-pt-rPT/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"A iniciar a configuração…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configure o dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo ligado"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Ligado a \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Não foi possível ligar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Impossível ligar"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Experimente sincronizar manualmente com o dispositivo"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Experimente ativar o modo de sincronização no dispositivo"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos ao alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos com a sua conta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Disposit. guardado disponível"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Partilha"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"A ligar…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Concluir"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Ligar"</string>
diff --git a/nearby/halfsheet/res/values-pt/strings.xml b/nearby/halfsheet/res/values-pt/strings.xml
index b021b39..15d29d2 100644
--- a/nearby/halfsheet/res/values-pt/strings.xml
+++ b/nearby/halfsheet/res/values-pt/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando a configuração…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar dispositivo"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Conectado ao dispositivo \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Erro ao conectar"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Não foi possível conectar"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Tente parear o dispositivo manualmente"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Coloque o dispositivo no modo de pareamento"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispositivos ao alcance"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispositivos conectados à sua conta"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispositivo salvo disponível"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Por perto"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispositivos"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Conectando…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Concluído"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Salvar"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
diff --git a/nearby/halfsheet/res/values-ro/strings.xml b/nearby/halfsheet/res/values-ro/strings.xml
index 5b50f15..0335d01 100644
--- a/nearby/halfsheet/res/values-ro/strings.xml
+++ b/nearby/halfsheet/res/values-ro/strings.xml
@@ -18,12 +18,23 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Începe configurarea…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurați dispozitivul"</string>
+    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurează dispozitivul"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispozitivul s-a conectat"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Conectat la %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nu s-a putut conecta"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nu se poate conecta"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Încearcă asocierea manuală cu dispozitivul"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Încearcă să pui dispozitivul în modul de asociere"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Dispozitive în vecinătate"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Dispozitive conectate cu contul"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Dispozitivul este disponibil"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"În apropiere"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Dispozitive"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Se conectează…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterie: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Gata"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salvați"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectați"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurați"</string>
+    <string name="paring_action_save" msgid="6259357442067880136">"Salvează"</string>
+    <string name="paring_action_connect" msgid="4801102939608129181">"Conectează"</string>
+    <string name="paring_action_launch" msgid="8940808384126591230">"Configurează"</string>
     <string name="paring_action_settings" msgid="424875657242864302">"Setări"</string>
 </resources>
diff --git a/nearby/halfsheet/res/values-ru/strings.xml b/nearby/halfsheet/res/values-ru/strings.xml
index ee869df..d90b644 100644
--- a/nearby/halfsheet/res/values-ru/strings.xml
+++ b/nearby/halfsheet/res/values-ru/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Начинаем настройку…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Настройка устройства"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Устройство подключено"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Подключено к устройству \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ошибка подключения"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Не удалось подключиться"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Попробуйте подключиться к устройству вручную."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Переведите устройство в режим подключения."</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Устройства в зоне охвата"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Устройства с вашим аккаунтом"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Доступно сохранен. устройство"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Мое окружение"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Устройства"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Подключение…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батарея: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Сохранить"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Подключить"</string>
diff --git a/nearby/halfsheet/res/values-si/strings.xml b/nearby/halfsheet/res/values-si/strings.xml
index f4274c2..c9b96bb 100644
--- a/nearby/halfsheet/res/values-si/strings.xml
+++ b/nearby/halfsheet/res/values-si/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"පිහිටුවීම ආරම්භ කරමින්…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"උපාංගය පිහිටුවන්න"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"උපාංගය සම්බන්ධිතයි"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” වෙත සම්බන්ධ විය"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"සම්බන්ධ කළ නොහැකි විය"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"සම්බන්ධ වීමට නොහැකි වේ"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"උපාංගය වෙත හස්තීයව යුගල කිරීමට උත්සාහ කරන්න"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"උපාංගය යුගල කිරීමේ ප්‍රකාරයට දැමීමට උත්සාහ කරන්න"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"ළඟා විය හැකි උපාංග"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"ඔබේ ගිණුම සමග උපාංග"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"ඔබේ සුරැකි උපාංගය ලබා ගත හැක"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"අවට"</string>
+    <string name="common_devices" msgid="2635603125608104442">"උපාංග"</string>
+    <string name="common_connecting" msgid="160531481424245303">"සම්බන්ධ වෙමින්…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"බැටරිය: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"නිමයි"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"සුරකින්න"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"සම්බන්ධ කරන්න"</string>
diff --git a/nearby/halfsheet/res/values-sk/strings.xml b/nearby/halfsheet/res/values-sk/strings.xml
index 46c45af..f7ab21f 100644
--- a/nearby/halfsheet/res/values-sk/strings.xml
+++ b/nearby/halfsheet/res/values-sk/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Spúšťa sa nastavenie…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavte zariadenie"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Zariadenie bolo pripojené"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Pripojené k zariadeniu %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nepodarilo sa pripojiť"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nepodarilo sa pripojiť"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Skúste zariadenie spárovať ručne"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Prepnite zariadenie do párovacieho režimu"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Zariadenia v dosahu"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Zariadenia s vaším účtom"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Uložené zariadenie je dostupné"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Nablízku"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Zariadenia"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Pripája sa…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batéria: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Hotovo"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Uložiť"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Pripojiť"</string>
diff --git a/nearby/halfsheet/res/values-sl/strings.xml b/nearby/halfsheet/res/values-sl/strings.xml
index e4f3c91..9e9357c 100644
--- a/nearby/halfsheet/res/values-sl/strings.xml
+++ b/nearby/halfsheet/res/values-sl/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Začetek nastavitve …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavitev naprave"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Naprava je povezana"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Povezano z napravo »%s«"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezava ni mogoča"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Povezave ni mogoče vzpostaviti"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Poskusite ročno seznaniti napravo."</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Poskusite napravo preklopiti v način za seznanjanje."</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Naprave znotraj dosega"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Naprave z vašim računom"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Shranjena naprava je na voljo"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Bližina"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Naprave"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Povezovanje …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterija: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Končano"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Shrani"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
diff --git a/nearby/halfsheet/res/values-sq/strings.xml b/nearby/halfsheet/res/values-sq/strings.xml
index 9265d1f..538e9d6 100644
--- a/nearby/halfsheet/res/values-sq/strings.xml
+++ b/nearby/halfsheet/res/values-sq/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Po nis konfigurimin…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfiguro pajisjen"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Pajisja u lidh"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"U lidh me “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nuk mund të lidhej"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Nuk mund të lidhet"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Provo të çiftosh me pajisjen manualisht"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Provo ta vendosësh pajisjen në modalitetin e çiftimit"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Pajisjet që mund të arrish"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Pajisjet me llogarinë tënde"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Pajisja jote e ruajtur ofrohet"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Në afërsi"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Pajisjet"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Po lidhet…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Bateria: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"U krye"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Ruaj"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Lidh"</string>
diff --git a/nearby/halfsheet/res/values-sr/strings.xml b/nearby/halfsheet/res/values-sr/strings.xml
index 094be03..c4bcd19 100644
--- a/nearby/halfsheet/res/values-sr/strings.xml
+++ b/nearby/halfsheet/res/values-sr/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Подешавање се покреће…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Подесите уређај"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Уређај је повезан"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Повезани сте са уређајем %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Повезивање није успело"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Повезивање није успело"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Пробајте да упарите ручно са уређајем"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Пробајте да пребаците уређај у режим упаривања"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Уређаји у домету"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Уређаји повезани са налогом"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Сачувани уређај је доступан"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"У близини"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Уређаји"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Повезује се…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Батерија: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Сачувај"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Повежи"</string>
diff --git a/nearby/halfsheet/res/values-sv/strings.xml b/nearby/halfsheet/res/values-sv/strings.xml
index 297b7bc..b00091c 100644
--- a/nearby/halfsheet/res/values-sv/strings.xml
+++ b/nearby/halfsheet/res/values-sv/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Konfigureringen startas …"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurera enheten"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheten är ansluten"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Ansluten till %s"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Det gick inte att ansluta"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Det går inte att ansluta"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Testa att parkoppla enheten manuellt"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Testa att aktivera parkopplingsläget på enheten"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Enheter inom räckvidd"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Enheter med ditt konto"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"En sparad enhet är tillgänglig"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Närdelning"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Enheter"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Ansluter …"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batteri: %d %%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Klar"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Spara"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Anslut"</string>
diff --git a/nearby/halfsheet/res/values-sw/strings.xml b/nearby/halfsheet/res/values-sw/strings.xml
index bf0bfeb..238a288 100644
--- a/nearby/halfsheet/res/values-sw/strings.xml
+++ b/nearby/halfsheet/res/values-sw/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Inaanza Kuweka Mipangilio…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Weka mipangilio ya kifaa"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Kifaa kimeunganishwa"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Imeunganishwa kwenye “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Imeshindwa kuunganisha"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Imeshindwa kuunganisha"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Jaribu kuoanisha mwenyewe kwenye kifaa"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Jaribu kuweka kifaa katika hali ya kuoanisha"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Vifaa vilivyo karibu nawe"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Vifaa vilivyounganishwa kwenye akaunti yako"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Kifaa ulichohifadhi kinapatikana"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Uhamishaji wa Karibu"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Vifaa"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Inaunganisha…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Betri: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Imemaliza"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Hifadhi"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Unganisha"</string>
diff --git a/nearby/halfsheet/res/values-ta/strings.xml b/nearby/halfsheet/res/values-ta/strings.xml
index dfd67a6..baadcc2 100644
--- a/nearby/halfsheet/res/values-ta/strings.xml
+++ b/nearby/halfsheet/res/values-ta/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"அமைவைத் தொடங்குகிறது…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"சாதனத்தை அமையுங்கள்"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"சாதனம் இணைக்கப்பட்டது"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s” உடன் இணைக்கப்பட்டது"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"இணைக்க முடியவில்லை"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"இணைக்க முடியவில்லை"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"சாதனத்துடன் நீங்களாகவே இணைக்க முயலவும்"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"சாதனத்தை \'இணைத்தல் பயன்முறையில்\' வைக்கவும்"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"தொடர்பு வரம்பிலுள்ள சாதனங்கள்"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"உங்கள் கணக்குடன் இணைந்துள்ள சாதனங்கள்"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"நீங்கள் சேமித்த சாதனம் உள்ளது"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"அருகில் பகிர்தல்"</string>
+    <string name="common_devices" msgid="2635603125608104442">"சாதனங்கள்"</string>
+    <string name="common_connecting" msgid="160531481424245303">"இணைக்கிறது…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"பேட்டரி: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"முடிந்தது"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"சேமி"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"இணை"</string>
diff --git a/nearby/halfsheet/res/values-te/strings.xml b/nearby/halfsheet/res/values-te/strings.xml
index 87be145..cb8f91b 100644
--- a/nearby/halfsheet/res/values-te/strings.xml
+++ b/nearby/halfsheet/res/values-te/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"సెటప్ ప్రారంభమవుతోంది…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"పరికరాన్ని సెటప్ చేయండి"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"పరికరం కనెక్ట్ చేయబడింది"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"“%s”కు కనెక్ట్ చేయబడింది"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"కనెక్ట్ చేయడం సాధ్యపడలేదు"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"కనెక్ట్ చేయలేకపోయింది"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"పరికరానికి మాన్యువల్‌గా పెయిరింగ్ చేయడానికి ట్రై చేయండి"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"పరికరాన్ని పెయిరింగ్ మోడ్‌లో ఉంచడానికి ట్రై చేయండి"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"అందుబాటులో ఉన్న పరికరాలు"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"మీ ఖాతా ఉన్న పరికరాలు"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"మీ సేవ్ చేసిన పరికరం అందుబాటులో ఉంది"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"సమీపం"</string>
+    <string name="common_devices" msgid="2635603125608104442">"పరికరాలు"</string>
+    <string name="common_connecting" msgid="160531481424245303">"కనెక్ట్ అవుతోంది…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"బ్యాటరీ: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"పూర్తయింది"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"సేవ్ చేయండి"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"కనెక్ట్ చేయండి"</string>
diff --git a/nearby/halfsheet/res/values-th/strings.xml b/nearby/halfsheet/res/values-th/strings.xml
index bc4296b..f5c5c2e 100644
--- a/nearby/halfsheet/res/values-th/strings.xml
+++ b/nearby/halfsheet/res/values-th/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"กำลังเริ่มการตั้งค่า…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"ตั้งค่าอุปกรณ์"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"เชื่อมต่ออุปกรณ์แล้ว"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"เชื่อมต่อกับ \"%s\" แล้ว"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"เชื่อมต่อไม่ได้"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"เชื่อมต่อไม่ได้"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"ลองจับคู่อุปกรณ์ด้วยตนเอง"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"พยายามนำอุปกรณ์เข้าสู่โหมดการจับคู่"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"อุปกรณ์ที่อยู่ติดกัน"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"อุปกรณ์ที่มีบัญชีของคุณ"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"อุปกรณ์ที่บันทึกพร้อมใช้แล้ว"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"ใกล้เคียง"</string>
+    <string name="common_devices" msgid="2635603125608104442">"อุปกรณ์"</string>
+    <string name="common_connecting" msgid="160531481424245303">"กำลังเชื่อมต่อ…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"แบตเตอรี่: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"เสร็จสิ้น"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"บันทึก"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"เชื่อมต่อ"</string>
diff --git a/nearby/halfsheet/res/values-tl/strings.xml b/nearby/halfsheet/res/values-tl/strings.xml
index a6de0e8..a546da6 100644
--- a/nearby/halfsheet/res/values-tl/strings.xml
+++ b/nearby/halfsheet/res/values-tl/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Sinisimulan ang Pag-set Up…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"I-set up ang device"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Naikonekta na ang device"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Nakakonekta sa “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Hindi makakonekta"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Hindi makakonekta"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Subukang manual na magpares sa device"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Subukang ilagay sa pairing mode ang device"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Mga naaabot na device"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Mga device sa iyong account"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Available ang iyong na-save na device"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Kalapit"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Mga Device"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Kumokonekta…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Baterya: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Tapos na"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"I-save"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Kumonekta"</string>
diff --git a/nearby/halfsheet/res/values-tr/strings.xml b/nearby/halfsheet/res/values-tr/strings.xml
index cd5a6ea..a54c5e7 100644
--- a/nearby/halfsheet/res/values-tr/strings.xml
+++ b/nearby/halfsheet/res/values-tr/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Kurulum Başlatılıyor…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Cihazı kur"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Cihaz bağlandı"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"\"%s\" cihazına bağlanıldı"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Bağlanamadı"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Bağlantı kurulamadı"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Cihazla manuel olarak eşlemeyi deneyin"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Cihazı eşleme moduna geçirmeyi deneyin"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Erişilebilecek cihazlar"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Hesabınıza bağlı cihazlar"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Kayıtlı cihazınız kullanılabilir"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Yakındaki"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Cihazlar"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Bağlanıyor…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Pil seviyesi: %%%d"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Bitti"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Kaydet"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Bağlan"</string>
diff --git a/nearby/halfsheet/res/values-uk/strings.xml b/nearby/halfsheet/res/values-uk/strings.xml
index 242ca07..ab73c1f 100644
--- a/nearby/halfsheet/res/values-uk/strings.xml
+++ b/nearby/halfsheet/res/values-uk/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Запуск налаштування…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Налаштуйте пристрій"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Пристрій підключено"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Підключено до пристрою \"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не вдалося підключити"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Не вдалося підключитися"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Спробуйте підключитися до пристрою вручну"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Спробуйте ввімкнути на пристрої режим підключення"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Пристрої в радіусі дії"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Пристрої з вашим обліковим записом"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Збережений пристрій доступний"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Поблизу"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Пристрої"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Підключення…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Акумулятор: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Зберегти"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Підключити"</string>
diff --git a/nearby/halfsheet/res/values-ur/strings.xml b/nearby/halfsheet/res/values-ur/strings.xml
index 4a4a59c..a2b2038 100644
--- a/nearby/halfsheet/res/values-ur/strings.xml
+++ b/nearby/halfsheet/res/values-ur/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"سیٹ اپ شروع ہو رہا ہے…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"آلہ سیٹ اپ کریں"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"آلہ منسلک ہے"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"‏\"‎\"%s سے منسلک ہے"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"منسلک نہیں ہو سکا"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"منسلک ہونے سے قاصر"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"دستی طور پر آلے کے ساتھ جوڑا بنانا آزمائیں"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"آلے کو جوڑا بنانے والے موڈ میں رکھ کر آزمائیں"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"رسائی کے اندر آلات"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"آپ کے اکاؤنٹ سے منسلک آلات"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"آپ کا محفوظ کردہ آلہ دستیاب ہے"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"قریبی"</string>
+    <string name="common_devices" msgid="2635603125608104442">"آلات"</string>
+    <string name="common_connecting" msgid="160531481424245303">"منسلک ہو رہا ہے…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"‏بیٹری: ‎‎%d%%‎"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"ہو گیا"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"محفوظ کریں"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"منسلک کریں"</string>
diff --git a/nearby/halfsheet/res/values-uz/strings.xml b/nearby/halfsheet/res/values-uz/strings.xml
index 420512d..70c190a 100644
--- a/nearby/halfsheet/res/values-uz/strings.xml
+++ b/nearby/halfsheet/res/values-uz/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Sozlash boshlandi…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Qurilmani sozlash"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Qurilma ulandi"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Bunga ulandi: “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ulanmadi"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Ulanmadi"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Qurilmangizga odatiy usulda ulaning"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Qurilmada ulanish rejimini yoqing"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Atrofdagi qurilmalar"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Hisobingizga ulangan qurilmalar"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Saqlangan qurilmangiz mavjud"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Yaqin-atrofda"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Qurilmalar"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Ulanmoqda…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Batareya: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Tayyor"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Saqlash"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Ulanish"</string>
diff --git a/nearby/halfsheet/res/values-vi/strings.xml b/nearby/halfsheet/res/values-vi/strings.xml
index 9c1e052..e2ea467 100644
--- a/nearby/halfsheet/res/values-vi/strings.xml
+++ b/nearby/halfsheet/res/values-vi/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Đang bắt đầu thiết lập…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Thiết lập thiết bị"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Đã kết nối thiết bị"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Đã kết nối với “%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Không kết nối được"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Không thể kết nối"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Hãy thử ghép nối thủ công với thiết bị"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Hãy thử đưa thiết bị này vào chế độ ghép nối"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Thiết bị trong tầm tay"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Các thiết bị có tài khoản của bạn"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Thiết bị đã lưu của bạn có sẵn"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Lân cận"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Thiết bị"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Đang kết nối…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Pin: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Xong"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Lưu"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Kết nối"</string>
diff --git a/nearby/halfsheet/res/values-zh-rCN/strings.xml b/nearby/halfsheet/res/values-zh-rCN/strings.xml
index 482b5c4..8117bac 100644
--- a/nearby/halfsheet/res/values-zh-rCN/strings.xml
+++ b/nearby/halfsheet/res/values-zh-rCN/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"正在启动设置…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"设置设备"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"设备已连接"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"已连接到“%s”"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"无法连接"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"无法连接"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"请尝试手动与该设备配对"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"请尝试让设备进入配对模式"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"附近的设备"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"与您的帐号相关联的设备"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"您保存的设备已可供使用"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"附近"</string>
+    <string name="common_devices" msgid="2635603125608104442">"设备"</string>
+    <string name="common_connecting" msgid="160531481424245303">"正在连接…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"电量:%d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"保存"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"连接"</string>
diff --git a/nearby/halfsheet/res/values-zh-rHK/strings.xml b/nearby/halfsheet/res/values-zh-rHK/strings.xml
index 3ca73e6..d934f88 100644
--- a/nearby/halfsheet/res/values-zh-rHK/strings.xml
+++ b/nearby/halfsheet/res/values-zh-rHK/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"開始設定…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"設定裝置"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"已連接裝置"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"已連線至「%s」"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"無法連接"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"無法連線"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"嘗試手動配對裝置"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"嘗試讓裝置進入配對模式"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"附近的裝置"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"已連結您帳戶的裝置"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"您儲存的裝置已可使用"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"咫尺共享"</string>
+    <string name="common_devices" msgid="2635603125608104442">"裝置"</string>
+    <string name="common_connecting" msgid="160531481424245303">"正在連接…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"電量:%d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"儲存"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"連接"</string>
diff --git a/nearby/halfsheet/res/values-zh-rTW/strings.xml b/nearby/halfsheet/res/values-zh-rTW/strings.xml
index b4e680d..0c90ebb 100644
--- a/nearby/halfsheet/res/values-zh-rTW/strings.xml
+++ b/nearby/halfsheet/res/values-zh-rTW/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"正在啟動設定程序…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"設定裝置"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"裝置已連線"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"已連線到「%s」"</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"無法連線"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"無法連線"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"嘗試手動配對裝置"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"嘗試讓裝置進入配對模式"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"鄰近裝置"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"與你帳戶連結的裝置"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"你儲存的裝置已可使用"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"鄰近分享"</string>
+    <string name="common_devices" msgid="2635603125608104442">"裝置"</string>
+    <string name="common_connecting" msgid="160531481424245303">"連線中…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"電池電量:%d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"儲存"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"連線"</string>
diff --git a/nearby/halfsheet/res/values-zu/strings.xml b/nearby/halfsheet/res/values-zu/strings.xml
index 33fb405..3f26469 100644
--- a/nearby/halfsheet/res/values-zu/strings.xml
+++ b/nearby/halfsheet/res/values-zu/strings.xml
@@ -20,7 +20,18 @@
     <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iqalisa Ukusetha…"</string>
     <string name="fast_pair_title_setup" msgid="2894360355540593246">"Setha idivayisi"</string>
     <string name="fast_pair_device_ready" msgid="2903490346082833101">"Idivayisi ixhunyiwe"</string>
+    <string name="fast_pair_device_ready_with_device_name" msgid="2151967995692339422">"Ixhunywe ku-\"%s\""</string>
     <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ayikwazanga ukuxhuma"</string>
+    <string name="fast_pair_unable_to_connect" msgid="3661854812014294569">"Ayikwazanga ukuxhuma"</string>
+    <string name="fast_pair_unable_to_connect_description" msgid="3926830740860653891">"Zama ukubhangqa kule divayisi"</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" msgid="3197372738187738030">"Zama ukubeka le divayisi kumodi yokubhangqa"</string>
+    <string name="devices_within_reach_channel_name" msgid="876280551450910440">"Amadivayisi afinyelelekayo"</string>
+    <string name="devices_with_your_account_channel_name" msgid="8120067812798598102">"Amadivayisi ane-akhawunti yakho"</string>
+    <string name="fast_pair_your_device" msgid="3662423897069320840">"Idivayisi yakho elondoloziwe ikhona"</string>
+    <string name="common_nearby_title" msgid="5480324514713607015">"Eduze"</string>
+    <string name="common_devices" msgid="2635603125608104442">"Amadivayisi"</string>
+    <string name="common_connecting" msgid="160531481424245303">"Iyaxhuma…"</string>
+    <string name="common_battery_level" msgid="8748495823047456645">"Ibhethri: %d%%"</string>
     <string name="paring_action_done" msgid="6888875159174470731">"Kwenziwe"</string>
     <string name="paring_action_save" msgid="6259357442067880136">"Londoloza"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Xhuma"</string>
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
index 01a82e4..c1f53d4 100644
--- a/nearby/halfsheet/res/values/strings.xml
+++ b/nearby/halfsheet/res/values/strings.xml
@@ -35,8 +35,22 @@
       [CHAR LIMIT=30]
     -->
     <string name="fast_pair_device_ready">Device connected</string>
+    <string name="fast_pair_device_ready_with_device_name" description="Notification title combined with device name after we successfully pair with the audio device. For example: Connected to &quot;Tommy's Bose QC35.&quot; [BACKUP_MESSAGE_ID: 6018442069058338390]">Connected to \u201c%s\u201d</string>
     <!-- Title text shown when peripheral device fail to connect to phone. [CHAR_LIMIT=30] -->
     <string name="fast_pair_title_fail">Couldn\'t connect</string>
+    <string name="fast_pair_unable_to_connect" description="Notification title after a pairing has failed. [CHAR LIMIT=30]">Unable to connect</string>
+    <string name="fast_pair_unable_to_connect_description" description="Notification body after a pairing has failed. [CHAR LIMIT=120]">Try manually pairing to the device</string>
+    <string name="fast_pair_turn_on_bt_device_pairing_mode" description="Notification body after a pairing has failed. [CHAR LIMIT=120]">Try putting the device into pairing mode</string>
+
+    <!--
+      ============================================================
+      PAIRING NOTIFICATION
+      ============================================================
+    -->
+
+    <string name="devices_within_reach_channel_name" description="Notification channel for devices within reach. [CHAR LIMIT=37]">Devices within reach</string>
+    <string name="devices_with_your_account_channel_name" description="Notification channel for devices that are connected to the user's account. [CHAR LIMIT=37]">Devices with your account</string>
+    <string name="fast_pair_your_device" description="Notification title for devices that are recognized as being owned by you. [CHAR LIMIT=30]">Your saved device is available</string>
 
     <!--
       ============================================================
@@ -44,6 +58,14 @@
       ============================================================
     -->
 
+    <!-- Title for Nearby component [CHAR LIMIT=40] -->
+    <string name="common_nearby_title">Nearby</string>
+    <!-- The product name for devices notification and list view. [CHAR LIMIT=37]-->
+    <string name="common_devices">Devices</string>
+    <!-- Text used to indicate that a connection attempt is ongoing [CHAR LIMIT=20] -->
+    <string name="common_connecting">Connecting&#8230;</string>
+    <!-- Label describing the battery level, for example "Battery: 72%". [CHAR LIMIT=60] -->
+    <string name="common_battery_level">Battery: %d%%</string>
     <!--
       A button shown after paring process to dismiss the current activity.
       [CHAR LIMIT=30]
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index 07e5776..94f4ef4 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -18,13 +18,19 @@
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET;
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_HALF_SHEET_FOREGROUND_STATE;
+import static com.android.nearby.halfsheet.constants.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_FOREGROUND;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_IS_RETROACTIVE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_TYPE;
+import static com.android.nearby.halfsheet.constants.Constant.TAG;
+import static com.android.nearby.halfsheet.constants.FastPairConstants.EXTRA_MODEL_ID;
+import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
 import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
-import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
-import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
-import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -49,31 +55,6 @@
  * A class show Fast Pair related information in Half sheet format.
  */
 public class HalfSheetActivity extends FragmentActivity {
-
-    public static final String TAG = "FastPairHalfSheet";
-
-    public static final String EXTRA_HALF_SHEET_CONTENT =
-            "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
-    public static final String EXTRA_TITLE =
-            "com.android.nearby.halfsheet.HALF_SHEET_TITLE";
-    public static final String EXTRA_DESCRIPTION =
-            "com.android.nearby.halfsheet.HALF_SHEET_DESCRIPTION";
-    public static final String EXTRA_HALF_SHEET_ID =
-            "com.android.nearby.halfsheet.HALF_SHEET_ID";
-    public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE =
-            "com.android.nearby.halfsheet.HALF_SHEET_IS_RETROACTIVE";
-    public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR =
-            "com.android.nearby.halfsheet.HALF_SHEET_IS_SUBSEQUENT_PAIR";
-    public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
-    public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
-    // Intent extra contains the user gmail name eg. testaccount@gmail.com.
-    public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
-            "com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
-    public static final String EXTRA_HALF_SHEET_FOREGROUND =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
-    public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
     @Nullable
     private HalfSheetModuleFragment mHalfSheetModuleFragment;
     @Nullable
@@ -141,6 +122,10 @@
     @Override
     protected void onStart() {
         super.onStart();
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
+                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, true));
     }
 
     @Override
@@ -207,30 +192,48 @@
         finish();
     }
 
+
+    /**
+     * Changes the half sheet ban state to active.
+     * Sometimes users leave half sheet to go to fast pair info page,
+     * we do not want the behavior to be counted as dismiss.
+     */
+    public void sendBanStateResetBroadcast() {
+        if (mScanFastPairStoreItem == null) {
+            return;
+        }
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET)
+                        .putExtra(EXTRA_MODEL_ID, mScanFastPairStoreItem.getModelId()
+                                .toLowerCase(Locale.ROOT)));
+    }
+
     private void sendHalfSheetCancelBroadcast() {
         BroadcastUtils.sendBroadcast(
                 this,
                 new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
                         .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
-        if (mScanFastPairStoreItem != null) {
-            BroadcastUtils.sendBroadcast(
-                    this,
-                    new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL)
-                            .putExtra(EXTRA_MODEL_ID,
-                                    mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))
-                            .putExtra(EXTRA_HALF_SHEET_TYPE,
-                                    getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE))
-                            .putExtra(
-                                    EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
-                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
-                                            false))
-                            .putExtra(
-                                    EXTRA_HALF_SHEET_IS_RETROACTIVE,
-                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
-                                            false))
-                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()),
-                    ACCESS_FINE_LOCATION);
+        if (mScanFastPairStoreItem == null) {
+            return;
         }
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL)
+                        .putExtra(EXTRA_MODEL_ID,
+                                mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))
+                        .putExtra(EXTRA_HALF_SHEET_TYPE,
+                                getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE))
+                        .putExtra(
+                                EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
+                                getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
+                                        false))
+                        .putExtra(
+                                EXTRA_HALF_SHEET_IS_RETROACTIVE,
+                                getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
+                                        false))
+                        .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()),
+                ACCESS_FINE_LOCATION);
     }
 
     @Override
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java
new file mode 100644
index 0000000..65c76d1
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.nearby.halfsheet.constants;
+
+/**
+ * String constant for half sheet.
+ */
+public class Constant {
+    public static final String TAG = "FastPairHalfSheet";
+    private static final String PREFIX = "com.android.nearby.halfsheet.";
+
+    // Intent extra
+    public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
+    public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
+
+    public static final String EXTRA_TITLE = PREFIX + "HALF_SHEET_TITLE";
+    public static final String EXTRA_DESCRIPTION = PREFIX + "HALF_SHEET_DESCRIPTION";
+    public static final String EXTRA_HALF_SHEET_ID = PREFIX + "HALF_SHEET_ID";
+    public static final String EXTRA_HALF_SHEET_INFO = PREFIX + "HALF_SHEET";
+    public static final String EXTRA_HALF_SHEET_TYPE = PREFIX + "HALF_SHEET_TYPE";
+    public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME = PREFIX + "HALF_SHEET_ACCOUNT_NAME";
+    public static final String EXTRA_HALF_SHEET_CONTENT = PREFIX + "HALF_SHEET_CONTENT";
+    public static final String EXTRA_HALF_SHEET_FOREGROUND =
+            PREFIX + "EXTRA_HALF_SHEET_FOREGROUND";
+    public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE =
+            PREFIX + "HALF_SHEET_IS_RETROACTIVE";
+    public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR =
+            PREFIX + "HALF_SHEET_IS_SUBSEQUENT_PAIR";
+    public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE =
+            PREFIX + "EXTRA_HALF_SHEET_PAIRING_RESURFACE";
+
+    // Intent Actions
+    public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
+            PREFIX + "ACTION_HALF_SHEET_FOREGROUND_STATE";
+    public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL =
+            "com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL";
+    public static final String ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET =
+            "com.android.nearby.ACTION_FAST_PAIR_BAN_STATE_RESET";
+    public static final String ACTION_RESOURCES_APK =
+            "android.nearby.SHOW_HALFSHEET";
+    public static final String ACTION_FAST_PAIR = PREFIX + "ACTION_MAGIC_PAIR";
+
+    public static final String RESULT_FAIL = "RESULT_FAIL";
+    public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
+    public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING";
+
+    // Content url for help page about Fast Pair in half sheet.
+    // Todo(b/246007000): Add a flag to set up content url of the help page.
+    public static final String FAST_PAIR_HALF_SHEET_HELP_URL =
+            "https://support.google.com/android/answer/9075925";
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/FastPairConstants.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/FastPairConstants.java
new file mode 100644
index 0000000..7cfd33a
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/FastPairConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.nearby.halfsheet.constants;
+
+/** Constants to share with other team. */
+public class FastPairConstants {
+    private static final String PACKAGE_NAME = "com.android.server.nearby";
+    public static final String PREFIX = PACKAGE_NAME + ".common.bluetooth.fastpair.";
+
+    /** MODEL_ID item name for extended intent field. */
+    public static final String EXTRA_MODEL_ID = PREFIX + "MODEL_ID";
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java
similarity index 85%
rename from nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
rename to nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java
index 67d87e3..767c6d6 100644
--- a/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.nearby.common.fastpair.service;
+package com.android.nearby.halfsheet.constants;
 
 /** Handles intents to {@link com.android.server.nearby.fastpair.FastPairManager}. */
 public class UserActionHandlerBase {
@@ -25,5 +25,5 @@
     public static final String EXTRA_COMPANION_APP = ACTION_PREFIX + "EXTRA_COMPANION_APP";
     public static final String EXTRA_MAC_ADDRESS = PREFIX + "EXTRA_MAC_ADDRESS";
 
+    public static final String ACTION_FAST_PAIR = ACTION_PREFIX + "ACTION_FAST_PAIR";
 }
-
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
index 320965b..9f5c915 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -17,22 +17,24 @@
 
 import static android.text.TextUtils.isEmpty;
 
-import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.constants.Constant.ARG_FRAGMENT_STATE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BINDER;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BUNDLE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_DESCRIPTION;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_ACCOUNT_NAME;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_CONTENT;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_ID;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_TITLE;
+import static com.android.nearby.halfsheet.constants.Constant.FAST_PAIR_HALF_SHEET_HELP_URL;
+import static com.android.nearby.halfsheet.constants.Constant.RESULT_FAIL;
+import static com.android.nearby.halfsheet.constants.Constant.TAG;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
@@ -61,6 +63,7 @@
 import com.android.nearby.halfsheet.HalfSheetActivity;
 import com.android.nearby.halfsheet.R;
 import com.android.nearby.halfsheet.utils.FastPairUtils;
+import com.android.nearby.halfsheet.utils.HelpUtils;
 import com.android.nearby.halfsheet.utils.IconUtils;
 
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -183,6 +186,11 @@
                     new FastPairUiServiceClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
             mFastPairUiServiceClient.registerHalfSheetStateCallBack(this);
         }
+        if (args.containsKey(EXTRA_HALF_SHEET_CONTENT)) {
+            if (RESULT_FAIL.equals(args.getString(EXTRA_HALF_SHEET_CONTENT))) {
+                mPairStatus = PairStatusMetadata.Status.FAIL;
+            }
+        }
         if (args.containsKey(ARG_FRAGMENT_STATE)) {
             mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
         }
@@ -230,12 +238,12 @@
         if (icon != null) {
             mImage.setImageBitmap(icon);
         }
-        mConnectButton.setOnClickListener(v -> onConnectClick());
+        mConnectButton.setOnClickListener(v -> onConnectClicked());
         mCancelButton.setOnClickListener(v ->
                 ((HalfSheetActivity) getActivity()).onCancelClicked());
         mSettingsButton.setOnClickListener(v -> onSettingsClicked());
-        mSetupButton.setOnClickListener(v -> onSetupClick());
-
+        mSetupButton.setOnClickListener(v -> onSetupClicked());
+        mInfoIconButton.setOnClickListener(v -> onHelpClicked());
         return rootView;
     }
 
@@ -250,7 +258,14 @@
     public void onStart() {
         super.onStart();
         Log.v(TAG, "onStart: invalidate states");
-        invalidateState();
+        // If the fragmentState is not NOT_STARTED, it is because the fragment was just resumed from
+        // configuration change (e.g. rotating the screen or half-sheet resurface). Let's recover
+        // the UI directly.
+        if (mFragmentState != NOT_STARTED) {
+            setState(mFragmentState);
+        } else {
+            invalidateState();
+        }
     }
 
     @Override
@@ -266,7 +281,7 @@
         startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
     }
 
-    private void onSetupClick() {
+    private void onSetupClicked() {
         String companionApp =
                 FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
         Intent intent =
@@ -284,7 +299,7 @@
         }
     }
 
-    private void onConnectClick() {
+    private void onConnectClicked() {
         if (mScanFastPairStoreItem == null) {
             Log.w(TAG, "No pairing related information in half sheet");
             return;
@@ -303,6 +318,12 @@
                         .build());
     }
 
+    private void onHelpClicked() {
+        HelpUtils.showHelpPage(getContext(), FAST_PAIR_HALF_SHEET_HELP_URL);
+        ((HalfSheetActivity) getActivity()).sendBanStateResetBroadcast();
+        getActivity().finish();
+    }
+
     // Receives callback from service.
     @Override
     public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) {
@@ -475,8 +496,7 @@
             case FAILED:
                 return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription();
             case PAIRED_UNLAUNCHABLE:
-                getString(R.string.fast_pair_device_ready);
-            // fall through
+                return getString(R.string.fast_pair_device_ready);
             case FOUND_DEVICE:
             case NOT_STARTED:
                 return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription();
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
index f1db4d0..d87c015 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
@@ -15,7 +15,7 @@
  */
 package com.android.nearby.halfsheet.fragment;
 
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.constants.Constant.TAG;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
 
 import android.os.Bundle;
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
index 00a365c..a1588a9 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -15,8 +15,8 @@
  */
 package com.android.nearby.halfsheet.utils;
 
-import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
-import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
+import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.ACTION_FAST_PAIR;
+import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.EXTRA_COMPANION_APP;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java
new file mode 100644
index 0000000..98f2242
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.nearby.halfsheet.utils;
+
+import static com.android.nearby.halfsheet.constants.Constant.TAG;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * Util class for launching help page in Fast Pair.
+ */
+public class HelpUtils {
+    /**
+     * Sets up the info button to launch a help page
+     */
+    public static void showHelpPage(Context context, String url)  {
+        requireNonNull(context, "context cannot be null");
+        requireNonNull(url, "url cannot be null");
+
+        try {
+            context.startActivity(createHelpPageIntent(url));
+        } catch (ActivityNotFoundException e) {
+            Log.w(TAG, "Failed to find activity for url " + url, e);
+        }
+    }
+
+    /**
+     * Creates the intent for help page
+     */
+    private static Intent createHelpPageIntent(String url) {
+        return new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
index 218c756..e547369 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
@@ -16,17 +16,13 @@
 
 package com.android.nearby.halfsheet.utils;
 
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.constants.Constant.TAG;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
 
 /**
  * Utility class for icon size verification.
@@ -85,49 +81,12 @@
                     BitmapFactory.decodeByteArray(imageData, /* offset= */ 0, size);
             if (IconUtils.isIconSizeCorrect(icon)) {
                 // Do not add background for Half Sheet.
-                return IconUtils.addWhiteCircleBackground(icon);
+                return icon;
             }
         } catch (OutOfMemoryError e) {
             Log.w(TAG, "getIcon: Failed to decode icon, returning null.", e);
         }
         return null;
     }
-
-    /** Adds a circular, white background to the bitmap. */
-    @Nullable
-    public static Bitmap addWhiteCircleBackground(Bitmap bitmap) {
-        if (bitmap == null) {
-            Log.w(TAG, "addWhiteCircleBackground: Bitmap is null, not adding background.");
-            return null;
-        }
-
-        if (bitmap.getWidth() != bitmap.getHeight()) {
-            Log.w(TAG, "addWhiteCircleBackground: Bitmap dimensions not square. Skipping"
-                    + "adding background.");
-            return bitmap;
-        }
-
-        int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENT);
-        Bitmap bitmapWithBackground =
-                Bitmap.createBitmap(
-                        bitmap.getWidth() + (2 * padding),
-                        bitmap.getHeight() + (2 * padding),
-                        bitmap.getConfig());
-        Canvas canvas = new Canvas(bitmapWithBackground);
-        Paint paint = new Paint();
-        paint.setColor(
-                ColorUtils.setAlphaComponent(
-                        Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA)));
-        paint.setStyle(Paint.Style.FILL);
-        paint.setAntiAlias(true);
-        canvas.drawCircle(
-                bitmapWithBackground.getWidth() / 2,
-                bitmapWithBackground.getHeight() / 2,
-                bitmapWithBackground.getWidth() / 2,
-                paint);
-        canvas.drawBitmap(bitmap, padding, padding, null);
-
-        return bitmapWithBackground;
-    }
 }
 
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index ef07bb9..d860048 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -24,23 +24,6 @@
     ],
 }
 
-filegroup {
-    name: "nearby-service-string-res",
-    srcs: [
-        "java/**/Constant.java",
-        "java/**/UserActionHandlerBase.java",
-        "java/**/UserActionHandler.java",
-        "java/**/FastPairConstants.java",
-    ],
-}
-
-java_library {
-    name: "nearby-service-string",
-    srcs: [":nearby-service-string-res"],
-    libs: ["framework-bluetooth"],
-    sdk_version: "module_current",
-}
-
 // Common lib for nearby end-to-end testing.
 java_library {
     name: "nearby-common-lib",
@@ -90,6 +73,7 @@
         "framework-configinfrastructure",
         "framework-connectivity-t.impl",
         "framework-statsd",
+        "HalfSheetUX",
     ],
     static_libs: [
         "androidx.core_core",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
index 8fdac87..0ed8fff 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
@@ -18,25 +18,40 @@
 
 import android.provider.DeviceConfig;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * A utility class for encapsulating Nearby feature flag configurations.
  */
 public class NearbyConfiguration {
 
     /**
-     * Flag use to enable presence legacy broadcast.
+     * Flag used to enable presence legacy broadcast.
      */
     public static final String NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY =
             "nearby_enable_presence_broadcast_legacy";
+    /**
+     * Flag used to for minimum nano app version to make Nearby CHRE scan work.
+     */
+    public static final String NEARBY_MAINLINE_NANO_APP_MIN_VERSION =
+            "nearby_mainline_nano_app_min_version";
+
+    /**
+     * Flag used to allow test mode and customization.
+     */
+    public static final String NEARBY_SUPPORT_TEST_APP = "nearby_support_test_app";
 
     private boolean mEnablePresenceBroadcastLegacy;
 
+    private int mNanoAppMinVersion;
+
+    private boolean mSupportTestApp;
+
     public NearbyConfiguration() {
         mEnablePresenceBroadcastLegacy = getDeviceConfigBoolean(
                 NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, false /* defaultValue */);
-
+        mNanoAppMinVersion = getDeviceConfigInt(
+                NEARBY_MAINLINE_NANO_APP_MIN_VERSION, 0 /* defaultValue */);
+        mSupportTestApp = getDeviceConfigBoolean(
+                NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);
     }
 
     /**
@@ -46,13 +61,28 @@
         return mEnablePresenceBroadcastLegacy;
     }
 
+    public int getNanoAppMinVersion() {
+        return mNanoAppMinVersion;
+    }
+
+    /**
+     * @return {@code true} when in test mode and allows customization.
+     */
+    public boolean isTestAppSupported() {
+        return mSupportTestApp;
+    }
+
     private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
         final String value = getDeviceConfigProperty(name);
         return value != null ? Boolean.parseBoolean(value) : defaultValue;
     }
 
-    @VisibleForTesting
-    protected String getDeviceConfigProperty(String name) {
+    private int getDeviceConfigInt(final String name, final int defaultValue) {
+        final String value = getDeviceConfigProperty(name);
+        return value != null ? Integer.parseInt(value) : defaultValue;
+    }
+
+    private String getDeviceConfigProperty(String name) {
         return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TETHERING, name);
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 5ebf1e5..b70930a 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.hardware.location.ContextHubManager;
 import android.nearby.BroadcastRequestParcelable;
 import android.nearby.IBroadcastListener;
@@ -36,13 +37,14 @@
 import android.nearby.IScanListener;
 import android.nearby.NearbyManager;
 import android.nearby.ScanRequest;
+import android.nearby.aidl.IOffloadCallback;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
 import com.android.server.nearby.fastpair.FastPairManager;
-import com.android.server.nearby.injector.ContextHubManagerAdapter;
 import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.PresenceManager;
 import com.android.server.nearby.provider.BroadcastProviderManager;
 import com.android.server.nearby.provider.DiscoveryProviderManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
@@ -53,10 +55,16 @@
 /** Service implementing nearby functionality. */
 public class NearbyService extends INearbyManager.Stub {
     public static final String TAG = "NearbyService";
+    // Sets to true to start BLE scan from PresenceManager for manual testing.
+    public static final Boolean MANUAL_TEST = false;
+    // Sets to true to support Mainline Test App.
+    // This will disable BLE privilege check and legacy broadcast support check.
+    public static final Boolean SUPPORT_TEST_APP = false;
 
     private final Context mContext;
     private Injector mInjector;
     private final FastPairManager mFastPairManager;
+    private final PresenceManager mPresenceManager;
     private final BroadcastReceiver mBluetoothReceiver =
             new BroadcastReceiver() {
                 @Override
@@ -84,6 +92,7 @@
         mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector);
         final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
         mFastPairManager = new FastPairManager(lcw);
+        mPresenceManager = new PresenceManager(lcw);
     }
 
     @VisibleForTesting
@@ -100,10 +109,7 @@
         CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
         DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity);
 
-        if (mProviderManager.registerScanListener(scanRequest, listener, identity)) {
-            return NearbyManager.ScanStatus.SUCCESS;
-        }
-        return NearbyManager.ScanStatus.ERROR;
+        return mProviderManager.registerScanListener(scanRequest, listener, identity);
     }
 
     @Override
@@ -140,6 +146,11 @@
         mBroadcastProviderManager.stopBroadcast(listener);
     }
 
+    @Override
+    public void queryOffloadScanSupport(IOffloadCallback callback) {
+
+    }
+
     /**
      * Called by the service initializer.
      *
@@ -157,16 +168,22 @@
                 FastPairDataProvider.init(mContext);
                 break;
             case PHASE_BOOT_COMPLETED:
+                // mInjector needs to be initialized before mProviderManager.
                 if (mInjector instanceof SystemInjector) {
                     // The nearby service must be functioning after this boot phase.
                     ((SystemInjector) mInjector).initializeBluetoothAdapter();
                     // Initialize ContextManager for CHRE scan.
-                    ((SystemInjector) mInjector).initializeContextHubManagerAdapter();
+                    ((SystemInjector) mInjector).initializeContextHubManager();
                 }
+                mProviderManager.init();
                 mContext.registerReceiver(
                         mBluetoothReceiver,
                         new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
                 mFastPairManager.initiate();
+                // Only enable for manual Presence test on device.
+                if (MANUAL_TEST) {
+                    mPresenceManager.initiate();
+                }
                 break;
         }
     }
@@ -178,15 +195,17 @@
      */
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private static void enforceBluetoothPrivilegedPermission(Context context) {
-        context.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
-                "Need BLUETOOTH PRIVILEGED permission");
+        if (!SUPPORT_TEST_APP) {
+            context.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+                    "Need BLUETOOTH PRIVILEGED permission");
+        }
     }
 
     private static final class SystemInjector implements Injector {
         private final Context mContext;
         @Nullable private BluetoothAdapter mBluetoothAdapter;
-        @Nullable private ContextHubManagerAdapter mContextHubManagerAdapter;
+        @Nullable private ContextHubManager mContextHubManager;
         @Nullable private AppOpsManager mAppOpsManager;
 
         SystemInjector(Context context) {
@@ -201,8 +220,8 @@
 
         @Override
         @Nullable
-        public ContextHubManagerAdapter getContextHubManagerAdapter() {
-            return mContextHubManagerAdapter;
+        public ContextHubManager getContextHubManager() {
+            return mContextHubManager;
         }
 
         @Override
@@ -222,15 +241,13 @@
             mBluetoothAdapter = manager.getAdapter();
         }
 
-        synchronized void initializeContextHubManagerAdapter() {
-            if (mContextHubManagerAdapter != null) {
+        synchronized void initializeContextHubManager() {
+            if (mContextHubManager != null) {
                 return;
             }
-            ContextHubManager manager = mContext.getSystemService(ContextHubManager.class);
-            if (manager == null) {
-                return;
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONTEXT_HUB)) {
+                mContextHubManager = mContext.getSystemService(ContextHubManager.class);
             }
-            mContextHubManagerAdapter = new ContextHubManagerAdapter(manager);
         }
 
         synchronized void initializeAppOpsManager() {
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
index 23d5170..dc4e11e 100644
--- a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
+++ b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
@@ -500,16 +500,16 @@
             return false;
         }
         BleFilter other = (BleFilter) obj;
-        return mDeviceName.equals(other.mDeviceName)
-                && mDeviceAddress.equals(other.mDeviceAddress)
+        return equal(mDeviceName, other.mDeviceName)
+                && equal(mDeviceAddress, other.mDeviceAddress)
                 && mManufacturerId == other.mManufacturerId
                 && Arrays.equals(mManufacturerData, other.mManufacturerData)
                 && Arrays.equals(mManufacturerDataMask, other.mManufacturerDataMask)
-                && mServiceDataUuid.equals(other.mServiceDataUuid)
+                && equal(mServiceDataUuid, other.mServiceDataUuid)
                 && Arrays.equals(mServiceData, other.mServiceData)
                 && Arrays.equals(mServiceDataMask, other.mServiceDataMask)
-                && mServiceUuid.equals(other.mServiceUuid)
-                && mServiceUuidMask.equals(other.mServiceUuidMask);
+                && equal(mServiceUuid, other.mServiceUuid)
+                && equal(mServiceUuidMask, other.mServiceUuidMask);
     }
 
     /** Builder class for {@link BleFilter}. */
@@ -743,4 +743,11 @@
         }
         return osFilterBuilder.build();
     }
+
+    /**
+     * equal() method for two possibly-null objects
+     */
+    private static boolean equal(@Nullable Object obj1, @Nullable Object obj2) {
+        return obj1 == obj2 || (obj1 != null && obj1.equals(obj2));
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
index f27899f..b4f46f8 100644
--- a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
+++ b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
@@ -46,6 +46,12 @@
     /** Model ID in {@link #getFastPairRecord()}. */
     public static final byte[] FAST_PAIR_MODEL_ID = Hex.stringToBytes("AABBCC");
 
+    /** An arbitrary BLE device address. */
+    public static final String DEVICE_ADDRESS = "00:00:00:00:00:01";
+
+    /** Arbitrary RSSI (Received Signal Strength Indicator). */
+    public static final int RSSI = -72;
+
     /** @see #getFastPairRecord() */
     public static byte[] newFastPairRecord(byte header, byte[] modelId) {
         return newFastPairRecord(
@@ -61,6 +67,45 @@
                         Hex.bytesToStringUppercase(serviceData)));
     }
 
+    // This is an example extended inquiry response for a phone with PANU
+    // and Hands-free Audio Gateway
+    public static byte[] eir_1 = {
+            0x06, // Length of this Data
+            0x09, // <<Complete Local Name>>
+            'P',
+            'h',
+            'o',
+            'n',
+            'e',
+            0x05, // Length of this Data
+            0x03, // <<Complete list of 16-bit Service UUIDs>>
+            0x15,
+            0x11, // PANU service class UUID
+            0x1F,
+            0x11, // Hands-free Audio Gateway service class UUID
+            0x01, // Length of this data
+            0x05, // <<Complete list of 32-bit Service UUIDs>>
+            0x11, // Length of this data
+            0x07, // <<Complete list of 128-bit Service UUIDs>>
+            0x01,
+            0x02,
+            0x03,
+            0x04,
+            0x05,
+            0x06,
+            0x07,
+            0x08, // Made up UUID
+            0x11,
+            0x12,
+            0x13,
+            0x14,
+            0x15,
+            0x16,
+            0x17,
+            0x18, //
+            0x00 // End of Data (Not transmitted over the air
+    };
+
     // This is an example of advertising data with AD types
     public static byte[] adv_1 = {
             0x02, // Length of this Data
@@ -138,4 +183,46 @@
             0x00
     };
 
+    // An Eddystone UID frame. go/eddystone for more info
+    public static byte[] eddystone_header_and_uuid = {
+            // BLE Flags
+            (byte) 0x02,
+            (byte) 0x01,
+            (byte) 0x06,
+            // Service UUID
+            (byte) 0x03,
+            (byte) 0x03,
+            (byte) 0xaa,
+            (byte) 0xfe,
+            // Service data header
+            (byte) 0x17,
+            (byte) 0x16,
+            (byte) 0xaa,
+            (byte) 0xfe,
+            // Eddystone frame type
+            (byte) 0x00,
+            // Ranging data
+            (byte) 0xb3,
+            // Eddystone ID namespace
+            (byte) 0x0a,
+            (byte) 0x09,
+            (byte) 0x08,
+            (byte) 0x07,
+            (byte) 0x06,
+            (byte) 0x05,
+            (byte) 0x04,
+            (byte) 0x03,
+            (byte) 0x02,
+            (byte) 0x01,
+            // Eddystone ID instance
+            (byte) 0x16,
+            (byte) 0x15,
+            (byte) 0x14,
+            (byte) 0x13,
+            (byte) 0x12,
+            (byte) 0x11,
+            // RFU
+            (byte) 0x00,
+            (byte) 0x00
+    };
 }
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java
index 637cd03..f6e77e6 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java
@@ -17,6 +17,7 @@
 package com.android.server.nearby.common.bluetooth.fastpair;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -51,7 +52,8 @@
             return this.mByteOrder.equals(byteOrder) ? getBytes() : reverse(getBytes());
         }
 
-        private static byte[] reverse(byte[] bytes) {
+        @VisibleForTesting
+        static byte[] reverse(byte[] bytes) {
             byte[] reversedBytes = new byte[bytes.length];
             for (int i = 0; i < bytes.length; i++) {
                 reversedBytes[i] = bytes[bytes.length - i - 1];
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
index 0b50dfd..7a0548b 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
@@ -133,14 +133,11 @@
             Event that = (Event) o;
             return this.mEventCode == that.getEventCode()
                     && this.mTimestamp == that.getTimestamp()
-                    && (this.mProfile == null
-                        ? that.getProfile() == null : this.mProfile.equals(that.getProfile()))
                     && (this.mBluetoothDevice == null
-                        ? that.getBluetoothDevice() == null :
-                            this.mBluetoothDevice.equals(that.getBluetoothDevice()))
-                    && (this.mException == null
-                        ?  that.getException() == null :
-                            this.mException.equals(that.getException()));
+                    ? that.getBluetoothDevice() == null :
+                    this.mBluetoothDevice.equals(that.getBluetoothDevice()))
+                    && (this.mProfile == null
+                    ? that.getProfile() == null : this.mProfile.equals(that.getProfile()));
         }
         return false;
     }
@@ -150,7 +147,6 @@
         return Objects.hash(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException);
     }
 
-
     /**
      * Builder
      */
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java
index 0ff1bf2..008891f 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java
@@ -16,15 +16,12 @@
 
 package com.android.server.nearby.common.bluetooth.fastpair;
 
+import static com.android.nearby.halfsheet.constants.FastPairConstants.PREFIX;
+
 import android.bluetooth.BluetoothDevice;
 
 /** Constants to share with other team. */
 public class FastPairConstants {
-    private static final String PACKAGE_NAME = "com.android.server.nearby";
-    private static final String PREFIX = PACKAGE_NAME + ".common.bluetooth.fastpair.";
-
-    /** MODEL_ID item name for extended intent field. */
-    public static final String EXTRA_MODEL_ID = PREFIX + "MODEL_ID";
     /** CONNECTION_ID item name for extended intent field. */
     public static final String EXTRA_CONNECTION_ID = PREFIX + "CONNECTION_ID";
     /** BLUETOOTH_MAC_ADDRESS item name for extended intent field. */
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 789ef59..60afa31 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
@@ -1993,7 +1993,8 @@
         return getLeState(bluetoothAdapter);
     }
 
-    private static int getLeState(android.bluetooth.BluetoothAdapter adapter) {
+    @VisibleForTesting
+    static int getLeState(android.bluetooth.BluetoothAdapter adapter) {
         try {
             return (Integer) Reflect.on(adapter).withMethod("getLeState").get();
         } catch (ReflectionException e) {
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
index bb7b71b..eb5bad5 100644
--- 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
@@ -1042,104 +1042,6 @@
      */
     public static Builder builder() {
         return new 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);
-    }
-
-    /**
-     * Constructs a builder from GmsLog.
-     */
-    // TODO(b/206668142): remove this builder once api is ready.
-    public static Builder builderFromGmsLog() {
-        return new Preferences.Builder()
                 .setGattOperationTimeoutSeconds(10)
                 .setGattConnectionTimeoutSeconds(15)
                 .setBluetoothToggleTimeoutSeconds(10)
@@ -1158,10 +1060,15 @@
                 .setBluetoothStatePollingMillis(1000)
                 .setNumAttempts(2)
                 .setEnableBrEdrHandover(false)
-                .setBrHandoverDataCharacteristicId((short) 11265)
-                .setBluetoothSigDataCharacteristicId((short) 11266)
-                .setFirmwareVersionCharacteristicId((short) 10790)
-                .setBrTransportBlockDataDescriptorId((short) 11267)
+                .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)
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
index 5b45f61..b2002c5 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
@@ -187,12 +187,6 @@
         return mWrappedBluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid);
     }
 
-    /** See {@link android.bluetooth.BluetoothDevice#setPin(byte[])}. */
-    @TargetApi(19)
-    public boolean setPairingConfirmation(byte[] pin) {
-        return mWrappedBluetoothDevice.setPin(pin);
-    }
-
     /** See {@link android.bluetooth.BluetoothDevice#setPairingConfirmation(boolean)}. */
     public boolean setPairingConfirmation(boolean confirm) {
         return mWrappedBluetoothDevice.setPairingConfirmation(confirm);
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
index 3f6f361..d4873fd 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
@@ -51,6 +51,11 @@
         return new BluetoothGattServer(instance);
     }
 
+    /** Unwraps a Bluetooth Gatt server. */
+    public android.bluetooth.BluetoothGattServer unwrap() {
+        return mWrappedInstance;
+    }
+
     /**
      * See {@link android.bluetooth.BluetoothGattServer#connect(
      * android.bluetooth.BluetoothDevice, boolean)}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
index 6fe4432..b2c61ab 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -71,4 +71,10 @@
         }
         return new BluetoothLeAdvertiser(bluetoothLeAdvertiser);
     }
+
+    /** Unwraps a Bluetooth LE advertiser. */
+    @Nullable
+    public android.bluetooth.le.BluetoothLeAdvertiser unwrap() {
+        return mWrappedInstance;
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
index 8a13abe..9b3447e 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
@@ -77,6 +77,12 @@
         mWrappedBluetoothLeScanner.stopScan(callbackIntent);
     }
 
+    /** Unwraps a Bluetooth LE scanner. */
+    @Nullable
+    public android.bluetooth.le.BluetoothLeScanner unwrap() {
+        return mWrappedBluetoothLeScanner;
+    }
+
     /** Wraps a Bluetooth LE scanner. */
     @Nullable
     public static BluetoothLeScanner wrap(
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
index f8b43a6..2003335 100644
--- a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
+++ b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
@@ -110,7 +110,8 @@
         throw new IllegalStateException(errorMessage);
     }
 
-    private String getUnboundErrorMessage(Class<?> type) {
+    @VisibleForTesting
+    String getUnboundErrorMessage(Class<?> type) {
         StringBuilder sb = new StringBuilder();
         sb.append("Unbound type: ").append(type.getName()).append("\n").append(
                 "Searched locators:\n");
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
index 0695b5f..f35703f 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
@@ -30,14 +30,4 @@
      * Tag for Fast Pair service related logs.
      */
     public static final String TAG = "FastPairService";
-
-    public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
-    public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
-    public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL =
-            "com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL";
-    public static final String EXTRA_HALF_SHEET_INFO =
-            "com.android.nearby.halfsheet.HALF_SHEET";
-    public static final String EXTRA_HALF_SHEET_TYPE =
-            "com.android.nearby.halfsheet.HALF_SHEET_TYPE";
-    public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING";
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index 2ecce47..412b738 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -36,7 +36,9 @@
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
 import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
+import com.android.server.nearby.util.ArrayUtils;
 import com.android.server.nearby.util.DataUtils;
 import com.android.server.nearby.util.Hex;
 
@@ -52,10 +54,18 @@
 public class FastPairAdvHandler {
     Context mContext;
     String mBleAddress;
-    // Need to be deleted after notification manager in use.
-    private boolean mIsFirst = false;
+    // TODO(b/247152236): Need to confirm the usage
+    // and deleted this after notification manager in use.
+    private boolean mIsFirst = true;
     private FastPairDataProvider mPairDataProvider;
     private static final double NEARBY_DISTANCE_THRESHOLD = 0.6;
+    // The byte, 0bLLLLTTTT, for battery length and type.
+    // Bit 0 - 3: type, 0b0011 (show UI indication) or 0b0100 (hide UI indication).
+    // Bit 4 - 7: length.
+    // https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification
+    private static final byte SHOW_UI_INDICATION = 0b0011;
+    private static final byte HIDE_UI_INDICATION = 0b0100;
+    private static final int LENGTH_ADVERTISEMENT_TYPE_BIT = 4;
 
     /** The types about how the bloomfilter is processed. */
     public enum ProcessBloomFilterType {
@@ -94,7 +104,7 @@
 
         if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
             byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
+            Log.v(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
             // Use api to get anti spoofing key from model id.
             try {
                 List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
@@ -114,115 +124,184 @@
                 }
                 Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
                         DataUtils.toScanFastPairStoreItem(
-                                response, mBleAddress,
+                                response, mBleAddress, Hex.bytesToStringLowercase(model),
                                 accountList.isEmpty() ? null : accountList.get(0).name));
             } catch (IllegalStateException e) {
                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
             }
         } else {
-            // Start to process bloom filter
+            // Start to process bloom filter. Yet to finish.
             try {
-                List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
-                byte[] bloomFilterByteArray = FastPairDecoder
-                        .getBloomFilter(fastPairDevice.getData());
-                byte[] bloomFilterSalt = FastPairDecoder
-                        .getBloomFilterSalt(fastPairDevice.getData());
-                if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
-                    return;
-                }
-                for (Account account : accountList) {
-                    List<Data.FastPairDeviceWithAccountKey> listDevices =
-                            mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
-                    Data.FastPairDeviceWithAccountKey recognizedDevice =
-                            findRecognizedDevice(listDevices,
-                                    new BloomFilter(bloomFilterByteArray,
-                                            new FastPairBloomFilterHasher()), bloomFilterSalt);
-
-                    if (recognizedDevice != null) {
-                        Log.d(TAG, "find matched device show notification to remind"
-                                + " user to pair");
-                        // Check the distance of the device if the distance is larger than the
-                        // threshold
-                        // do not show half sheet.
-                        if (!isNearby(fastPairDevice.getRssi(),
-                                recognizedDevice.getDiscoveryItem().getTxPower() == 0
-                                        ? fastPairDevice.getTxPower()
-                                        : recognizedDevice.getDiscoveryItem().getTxPower())) {
-                            return;
-                        }
-                        // Check if the device is already paired
-                        List<Cache.StoredFastPairItem> storedFastPairItemList =
-                                Locator.get(mContext, FastPairCacheManager.class)
-                                        .getAllSavedStoredFastPairItem();
-                        Cache.StoredFastPairItem recognizedStoredFastPairItem =
-                                findRecognizedDeviceFromCachedItem(storedFastPairItemList,
-                                        new BloomFilter(bloomFilterByteArray,
-                                                new FastPairBloomFilterHasher()), bloomFilterSalt);
-                        if (recognizedStoredFastPairItem != null) {
-                            // The bloomfilter is recognized in the cache so the device is paired
-                            // before
-                            Log.d(TAG, "bloom filter is recognized in the cache");
-                            continue;
-                        } else {
-                            Log.d(TAG, "bloom filter is recognized not paired before should"
-                                    + "show subsequent pairing notification");
-                            if (mIsFirst) {
-                                mIsFirst = false;
-                                // Get full info from api the initial request will only return
-                                // part of the info due to size limit.
-                                List<Data.FastPairDeviceWithAccountKey> resList =
-                                        mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
-                                                List.of(recognizedDevice.getAccountKey()
-                                                        .toByteArray()));
-                                if (resList != null && resList.size() > 0) {
-                                    //Saved device from footprint does not have ble address so
-                                    // fill ble address with current scan result.
-                                    Cache.StoredDiscoveryItem storedDiscoveryItem =
-                                            resList.get(0).getDiscoveryItem().toBuilder()
-                                                    .setMacAddress(
-                                                            fastPairDevice.getBluetoothAddress())
-                                                    .build();
-                                    Locator.get(mContext, FastPairController.class).pair(
-                                            new DiscoveryItem(mContext, storedDiscoveryItem),
-                                            resList.get(0).getAccountKey().toByteArray(),
-                                            /** companionApp=*/null);
-                                }
-                            }
-                        }
-
-                        return;
-                    }
-                }
+                subsequentPair(fastPairDevice);
             } catch (IllegalStateException e) {
-                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
+                Log.e(TAG, "handleBroadcast: subsequent pair failed", e);
+            }
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting
+    static byte[] getBloomFilterBytes(byte[] data) {
+        byte[] bloomFilterBytes = FastPairDecoder.getBloomFilter(data);
+        if (bloomFilterBytes == null) {
+            bloomFilterBytes = FastPairDecoder.getBloomFilterNoNotification(data);
+        }
+        if (ArrayUtils.isEmpty(bloomFilterBytes)) {
+            Log.d(TAG, "subsequentPair: bloomFilterByteArray empty");
+            return null;
+        }
+        return bloomFilterBytes;
+    }
+
+    private int getTxPower(FastPairDevice scannedDevice,
+            Data.FastPairDeviceWithAccountKey recognizedDevice) {
+        return recognizedDevice.getDiscoveryItem().getTxPower() == 0
+                ? scannedDevice.getTxPower()
+                : recognizedDevice.getDiscoveryItem().getTxPower();
+    }
+
+    private void subsequentPair(FastPairDevice scannedDevice) {
+        byte[] data = scannedDevice.getData();
+
+        if (ArrayUtils.isEmpty(data)) {
+            Log.d(TAG, "subsequentPair: no valid data");
+            return;
+        }
+
+        byte[] bloomFilterBytes = getBloomFilterBytes(data);
+        if (ArrayUtils.isEmpty(bloomFilterBytes)) {
+            Log.d(TAG, "subsequentPair: no valid bloom filter");
+            return;
+        }
+
+        byte[] salt = FastPairDecoder.getBloomFilterSalt(data);
+        if (ArrayUtils.isEmpty(salt)) {
+            Log.d(TAG, "subsequentPair: no valid salt");
+            return;
+        }
+        byte[] saltWithData = concat(salt, generateBatteryData(data));
+
+        List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
+        for (Account account : accountList) {
+            List<Data.FastPairDeviceWithAccountKey> devices =
+                    mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
+            Data.FastPairDeviceWithAccountKey recognizedDevice =
+                    findRecognizedDevice(devices,
+                            new BloomFilter(bloomFilterBytes,
+                                    new FastPairBloomFilterHasher()), saltWithData);
+            if (recognizedDevice == null) {
+                Log.v(TAG, "subsequentPair: recognizedDevice is null");
+                continue;
             }
 
+            // Check the distance of the device if the distance is larger than the
+            // threshold
+            if (!isNearby(scannedDevice.getRssi(), getTxPower(scannedDevice, recognizedDevice))) {
+                Log.v(TAG,
+                        "subsequentPair: the distance of the device is larger than the threshold");
+                return;
+            }
+
+            // Check if the device is already paired
+            List<Cache.StoredFastPairItem> storedFastPairItemList =
+                    Locator.get(mContext, FastPairCacheManager.class)
+                            .getAllSavedStoredFastPairItem();
+            Cache.StoredFastPairItem recognizedStoredFastPairItem =
+                    findRecognizedDeviceFromCachedItem(storedFastPairItemList,
+                            new BloomFilter(bloomFilterBytes,
+                                    new FastPairBloomFilterHasher()), saltWithData);
+            if (recognizedStoredFastPairItem != null) {
+                // The bloomfilter is recognized in the cache so the device is paired
+                // before
+                Log.d(TAG, "bloom filter is recognized in the cache");
+                continue;
+            }
+            showSubsequentNotification(account, scannedDevice, recognizedDevice);
         }
     }
 
+    private void showSubsequentNotification(Account account, FastPairDevice scannedDevice,
+            Data.FastPairDeviceWithAccountKey recognizedDevice) {
+        // Get full info from api the initial request will only return
+        // part of the info due to size limit.
+        List<Data.FastPairDeviceWithAccountKey> devicesWithAccountKeys =
+                mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
+                        List.of(recognizedDevice.getAccountKey().toByteArray()));
+        if (devicesWithAccountKeys == null || devicesWithAccountKeys.isEmpty()) {
+            Log.d(TAG, "No fast pair device with account key is found.");
+            return;
+        }
+
+        // Saved device from footprint does not have ble address.
+        // We need to fill ble address with current scan result.
+        Cache.StoredDiscoveryItem storedDiscoveryItem =
+                devicesWithAccountKeys.get(0).getDiscoveryItem().toBuilder()
+                        .setMacAddress(
+                                scannedDevice.getBluetoothAddress())
+                        .build();
+        // Show notification
+        FastPairNotificationManager fastPairNotificationManager =
+                Locator.get(mContext, FastPairNotificationManager.class);
+        DiscoveryItem item = new DiscoveryItem(mContext, storedDiscoveryItem);
+        Locator.get(mContext, FastPairCacheManager.class).saveDiscoveryItem(item);
+        fastPairNotificationManager.showDiscoveryNotification(item,
+                devicesWithAccountKeys.get(0).getAccountKey().toByteArray());
+    }
+
+    // Battery advertisement format:
+    // Byte 0: Battery length and type, Bit 0 - 3: type, Bit 4 - 7: length.
+    // Byte 1 - 3: Battery values.
+    // Reference:
+    // https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification
+    @VisibleForTesting
+    static byte[] generateBatteryData(byte[] data) {
+        byte[] batteryLevelNoNotification = FastPairDecoder.getBatteryLevelNoNotification(data);
+        boolean suppressBatteryNotification =
+                (batteryLevelNoNotification != null && batteryLevelNoNotification.length > 0);
+        byte[] batteryValues =
+                suppressBatteryNotification
+                        ? batteryLevelNoNotification
+                        : FastPairDecoder.getBatteryLevel(data);
+        if (ArrayUtils.isEmpty(batteryValues)) {
+            return new byte[0];
+        }
+        return generateBatteryData(suppressBatteryNotification, batteryValues);
+    }
+
+    @VisibleForTesting
+    static byte[] generateBatteryData(boolean suppressBatteryNotification, byte[] batteryValues) {
+        return concat(
+                new byte[] {
+                        (byte)
+                                (batteryValues.length << LENGTH_ADVERTISEMENT_TYPE_BIT
+                                        | (suppressBatteryNotification
+                                        ? HIDE_UI_INDICATION : SHOW_UI_INDICATION))
+                },
+                batteryValues);
+    }
+
     /**
      * Checks the bloom filter to see if any of the devices are recognized and should have a
      * notification displayed for them. A device is recognized if the account key + salt combination
      * is inside the bloom filter.
      */
     @Nullable
+    @VisibleForTesting
     static Data.FastPairDeviceWithAccountKey findRecognizedDevice(
             List<Data.FastPairDeviceWithAccountKey> devices, BloomFilter bloomFilter, byte[] salt) {
-        Log.d(TAG, "saved devices size in the account is " + devices.size());
         for (Data.FastPairDeviceWithAccountKey device : devices) {
             if (device.getAccountKey().toByteArray() == null || salt == null) {
                 return null;
             }
             byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
+
             StringBuilder sb = new StringBuilder();
             for (byte b : rotatedKey) {
                 sb.append(b);
             }
+
             if (bloomFilter.possiblyContains(rotatedKey)) {
-                Log.d(TAG, "match " + sb.toString());
                 return device;
-            } else {
-                Log.d(TAG, "not match " + sb.toString());
             }
         }
         return null;
@@ -249,5 +328,4 @@
     boolean isNearby(int rssi, int txPower) {
         return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD;
     }
-
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
index e1db7e5..447d199 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
@@ -129,11 +129,8 @@
                                 Log.d(TAG, "Incorrect state, ignore pairing");
                                 return;
                             }
-                            boolean useLargeNotifications =
-                                    item.getAuthenticationPublicKeySecp256R1() != null;
                             FastPairNotificationManager fastPairNotificationManager =
-                                    new FastPairNotificationManager(mContext, item,
-                                            useLargeNotifications);
+                                    Locator.get(mContext, FastPairNotificationManager.class);
                             FastPairHalfSheetManager fastPairHalfSheetManager =
                                     Locator.get(mContext, FastPairHalfSheetManager.class);
                             mFastPairCacheManager.saveDiscoveryItem(item);
@@ -169,7 +166,7 @@
             @Nullable byte[] accountKey,
             @Nullable String companionApp) {
         FastPairNotificationManager fastPairNotificationManager =
-                new FastPairNotificationManager(mContext, item, false);
+                Locator.get(mContext, FastPairNotificationManager.class);
         FastPairHalfSheetManager fastPairHalfSheetManager =
                 Locator.get(mContext, FastPairHalfSheetManager.class);
         PairingProgressHandlerBase pairingProgressHandlerBase =
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index f368080..1ef98e5 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,8 +16,16 @@
 
 package com.android.server.nearby.fastpair;
 
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR;
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET;
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_HALF_SHEET_FOREGROUND_STATE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_FOREGROUND;
+import static com.android.nearby.halfsheet.constants.FastPairConstants.EXTRA_MODEL_ID;
 import static com.android.server.nearby.fastpair.Constant.TAG;
 
+import static com.google.common.io.BaseEncoding.base16;
+
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.app.KeyguardManager;
@@ -89,7 +97,6 @@
 
     /** A notification ID which should be dismissed */
     public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
-    public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
 
     private static Executor sFastPairExecutor;
 
@@ -99,16 +106,73 @@
     final IntentFilter mIntentFilter;
     final Locator mLocator;
     private boolean mScanEnabled;
+    private final FastPairCacheManager mFastPairCacheManager;
 
     private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)
-                    || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
-                Log.d(TAG, "onReceive: ACTION_SCREEN_ON or boot complete.");
-                invalidateScan();
-            } else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
-                processBluetoothConnectionEvent(intent);
+            String action = intent.getAction();
+            switch (action) {
+                case Intent.ACTION_SCREEN_ON:
+                    Log.d(TAG, "onReceive: ACTION_SCREEN_ON");
+                    invalidateScan();
+                    break;
+                case Intent.ACTION_BOOT_COMPLETED:
+                    Log.d(TAG, "onReceive: ACTION_BOOT_COMPLETED.");
+                    invalidateScan();
+                    break;
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+                    Log.d(TAG, "onReceive: ACTION_BOND_STATE_CHANGED");
+                    processBluetoothConnectionEvent(intent);
+                    break;
+                case ACTION_HALF_SHEET_FOREGROUND_STATE:
+                    boolean state = intent.getBooleanExtra(EXTRA_HALF_SHEET_FOREGROUND, false);
+                    Log.d(TAG, "halfsheet report foreground state:  " + state);
+                    Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class)
+                            .setHalfSheetForeground(state);
+                    break;
+                case ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET:
+                    Log.d(TAG, "onReceive: ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET");
+                    String deviceModelId = intent.getStringExtra(EXTRA_MODEL_ID);
+                    if (deviceModelId == null) {
+                        Log.d(TAG, "HalfSheetManager reset device ban state skipped, "
+                                + "deviceModelId not found");
+                        break;
+                    }
+                    Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class)
+                            .resetBanState(deviceModelId);
+                    break;
+                case ACTION_FAST_PAIR_HALF_SHEET_CANCEL:
+                    Log.d(TAG, "onReceive: ACTION_FAST_PAIR_HALF_SHEET_CANCEL");
+                    String modelId = intent.getStringExtra(EXTRA_MODEL_ID);
+                    if (modelId == null) {
+                        Log.d(TAG, "skip half sheet cancel action, model id not found");
+                        break;
+                    }
+                    Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class)
+                            .dismiss(modelId);
+                    break;
+                case ACTION_FAST_PAIR:
+                    Log.d(TAG, "onReceive: ACTION_FAST_PAIR");
+                    String itemId = intent.getStringExtra(UserActionHandler.EXTRA_ITEM_ID);
+                    String accountKeyString = intent
+                            .getStringExtra(UserActionHandler.EXTRA_FAST_PAIR_SECRET);
+                    if (itemId == null || accountKeyString == null) {
+                        Log.d(TAG, "skip pair action, item id "
+                                + "or fast pair account key not found");
+                        break;
+                    }
+                    try {
+                        FastPairController controller =
+                                Locator.getFromContextWrapper(mLocatorContextWrapper,
+                                        FastPairController.class);
+                        if (mFastPairCacheManager != null) {
+                            controller.pair(mFastPairCacheManager.getDiscoveryItem(itemId),
+                                    base16().decode(accountKeyString), /* companionApp= */ null);
+                        }
+                    } catch (IllegalStateException e) {
+                        Log.e(TAG, "Cannot find FastPairController class", e);
+                    }
             }
         }
     };
@@ -120,6 +184,8 @@
         mLocator.bind(new FastPairModule());
         Rpcs.GetObservedDeviceResponse getObservedDeviceResponse =
                 Rpcs.GetObservedDeviceResponse.newBuilder().build();
+        mFastPairCacheManager =
+                Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
     }
 
     final ScanCallback mScanCallback = new ScanCallback() {
@@ -141,6 +207,11 @@
             byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
             Log.d(TAG, "lost model id" + Hex.bytesToStringLowercase(modelArray));
         }
+
+        @Override
+        public void onError(int errorCode) {
+            Log.w(TAG, "[FastPairManager] Scan error is " + errorCode);
+        }
     };
 
     /**
@@ -151,13 +222,16 @@
         mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
+        mIntentFilter.addAction(ACTION_FAST_PAIR_HALF_SHEET_CANCEL);
+        mIntentFilter.addAction(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET);
+        mIntentFilter.addAction(ACTION_HALF_SHEET_FOREGROUND_STATE);
+        mIntentFilter.addAction(ACTION_FAST_PAIR);
 
-        mLocatorContextWrapper.getContext()
-                .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
+        mLocatorContextWrapper.getContext().registerReceiver(mScreenBroadcastReceiver,
+                mIntentFilter, Context.RECEIVER_EXPORTED);
 
-        Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
         // Default false for now.
-        mScanEnabled = NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext());
+        mScanEnabled = NearbyManager.isFastPairScanEnabled(mLocatorContextWrapper.getContext());
         registerFastPairScanChangeContentObserver(mLocatorContextWrapper.getContentResolver());
     }
 
@@ -242,7 +316,7 @@
 
             String modelId = item.getTriggerId();
             Preferences.Builder prefsBuilder =
-                    Preferences.builderFromGmsLog()
+                    Preferences.builder()
                             .setEnableBrEdrHandover(false)
                             .setIgnoreDiscoveryError(true);
             pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
@@ -259,6 +333,13 @@
                     context, item.getMacAddress(),
                     prefsBuilder.build(),
                     null);
+            connection.setOnPairedCallback(
+                    address -> {
+                        Log.v(TAG, "connection on paired callback;");
+                        // TODO(b/259150992) add fill Bluetooth metadata values logic
+                        pairingProgressHandlerBase.onPairedCallbackCalled(
+                                connection, accountKey, footprints, address);
+                    });
             pairingProgressHandlerBase.onPairingSetupCompleted();
 
             FastPairConnection.SharedSecret sharedSecret;
@@ -354,12 +435,15 @@
     }
 
     private void registerFastPairScanChangeContentObserver(ContentResolver resolver) {
+        if (mFastPairScanChangeContentObserver != null) {
+            unregisterFastPairScanChangeContentObserver(resolver);
+        }
         mFastPairScanChangeContentObserver = new ContentObserver(ForegroundThread.getHandler()) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
                 super.onChange(selfChange, uri);
                 setScanEnabled(
-                        NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext()));
+                        NearbyManager.isFastPairScanEnabled(mLocatorContextWrapper.getContext()));
             }
         };
         try {
@@ -372,6 +456,15 @@
         }
     }
 
+    private void unregisterFastPairScanChangeContentObserver(ContentResolver resolver) {
+        try {
+            resolver.unregisterContentObserver(mFastPairScanChangeContentObserver);
+            mFastPairScanChangeContentObserver = null;
+        } catch (SecurityException | NullPointerException | IllegalArgumentException e) {
+            Log.w(TAG, "Failed to unregister FastPairScanChange content observer.", e);
+        }
+    }
+
     /**
      * Processed task in a background thread
      */
@@ -414,7 +507,7 @@
         NearbyManager nearbyManager = getNearbyManager();
         if (nearbyManager == null) {
             Log.w(TAG, "invalidateScan: "
-                    + "failed to start or stop scannning because NearbyManager is null.");
+                    + "failed to start or stop scanning because NearbyManager is null.");
             return;
         }
         if (mScanEnabled) {
@@ -444,8 +537,7 @@
                 processBackgroundTask(new Runnable() {
                     @Override
                     public void run() {
-                        mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class)
-                                .removeStoredFastPairItem(device.getAddress());
+                        mFastPairCacheManager.removeStoredFastPairItem(device.getAddress());
                     }
                 });
             }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
index d7946d1..1df4723 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
@@ -24,6 +24,7 @@
 import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
 import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
 
 import java.time.Clock;
 import java.time.Instant;
@@ -52,6 +53,9 @@
             locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager(context));
         } else if (type.equals(FastPairAdvHandler.class)) {
             locator.bind(FastPairAdvHandler.class, new FastPairAdvHandler(context));
+        } else if (type.equals(FastPairNotificationManager.class)) {
+            locator.bind(FastPairNotificationManager.class,
+                    new FastPairNotificationManager(context));
         } else if (type.equals(Clock.class)) {
             locator.bind(Clock.class, new Clock() {
                 @Override
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java b/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java
new file mode 100644
index 0000000..86dd44d
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.fastpair;
+
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorRes;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Utility to obtain the {@link com.android.nearby.halfsheet} {@link Resources}, in the
+ * HalfSheetUX APK.
+ * @hide
+ */
+public class HalfSheetResources {
+    @NonNull
+    private final Context mContext;
+
+    @Nullable
+    private Context mResourcesContext = null;
+
+    @Nullable
+    private static Context sTestResourcesContext = null;
+
+    public HalfSheetResources(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Convenience method to mock all resources for the duration of a test.
+     *
+     * Call with a null context to reset after the test.
+     */
+    @VisibleForTesting
+    public static void setResourcesContextForTest(@Nullable Context testContext) {
+        sTestResourcesContext = testContext;
+    }
+
+    /**
+     * Get the {@link Context} of the resources package.
+     */
+    @Nullable
+    public synchronized Context getResourcesContext() {
+        if (sTestResourcesContext != null) {
+            return sTestResourcesContext;
+        }
+
+        if (mResourcesContext != null) {
+            return mResourcesContext;
+        }
+
+        String packageName = PackageUtils.getHalfSheetApkPkgName(mContext);
+        if (packageName == null) {
+            Log.e(TAG, "Resolved package not found");
+            return null;
+        }
+        final Context pkgContext;
+        try {
+            pkgContext = mContext.createPackageContext(packageName, 0 /* flags */);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Resolved package not found");
+            return null;
+        }
+
+        mResourcesContext = pkgContext;
+        return pkgContext;
+    }
+
+    /**
+     * Get the {@link Resources} of the ServiceConnectivityResources APK.
+     */
+    public Resources get() {
+        return getResourcesContext().getResources();
+    }
+
+    /**
+     * Gets the {@code String} with given resource Id.
+     */
+    public String getString(@StringRes int id) {
+        return get().getString(id);
+    }
+
+    /**
+     * Gets the {@code String} with given resource Id and formatted arguments.
+     */
+    public String getString(@StringRes int id, Object... formatArgs) {
+        return get().getString(id, formatArgs);
+    }
+
+    /**
+     * Gets the {@link Drawable} with given resource Id.
+     */
+    public Drawable getDrawable(@DrawableRes int id) {
+        return get().getDrawable(id, getResourcesContext().getTheme());
+    }
+
+    /**
+     * Gets a themed color integer associated with a particular resource ID.
+     */
+    @ColorInt
+    public int getColor(@ColorRes int id) {
+        return get().getColor(id, getResourcesContext().getTheme());
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java b/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java
new file mode 100644
index 0000000..0ff8caf
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.fastpair;
+
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_RESOURCES_APK;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+import com.android.server.nearby.util.Environment;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class for package related methods.
+ */
+public class PackageUtils {
+
+    /**
+     * Gets the package name of HalfSheet.apk
+     */
+    @Nullable
+    public static String getHalfSheetApkPkgName(Context context) {
+        List<ResolveInfo> resolveInfos = context
+                .getPackageManager().queryIntentActivities(
+                        new Intent(ACTION_RESOURCES_APK),
+                        PackageManager.MATCH_SYSTEM_ONLY);
+
+        // remove apps that don't live in the nearby apex
+        resolveInfos.removeIf(info ->
+                !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
+
+        if (resolveInfos.isEmpty()) {
+            // Resource APK not loaded yet, print a stack trace to see where this is called from
+            Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
+                            + " APK is installed or package manager can't resolve correctly!",
+                    new IllegalStateException());
+            return null;
+        }
+
+        if (resolveInfos.size() > 1) {
+            // multiple apps found, log a warning, but continue
+            Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
+                    + resolveInfos.stream()
+                    .map(info -> info.activityInfo.applicationInfo.packageName)
+                    .collect(Collectors.joining(", ")));
+        }
+
+        // Assume the first ResolveInfo is the one we're looking for
+        ResolveInfo info = resolveInfos.get(0);
+        return info.activityInfo.applicationInfo.packageName;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
index 674633d..2b00ca5 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
@@ -16,16 +16,16 @@
 
 package com.android.server.nearby.fastpair;
 
-import com.android.server.nearby.common.fastpair.service.UserActionHandlerBase;
+import com.android.nearby.halfsheet.constants.UserActionHandlerBase;
 
 /**
  * User action handler class.
  */
 public class UserActionHandler extends UserActionHandlerBase {
 
-    public static final String EXTRA_DISCOVERY_ITEM = PREFIX + "EXTRA_DISCOVERY_ITEM";
+    public static final String EXTRA_ITEM_ID = PREFIX + "EXTRA_DISCOVERY_ITEM";
     public static final String EXTRA_FAST_PAIR_SECRET = PREFIX + "EXTRA_FAST_PAIR_SECRET";
-    public static final String ACTION_FAST_PAIR = ACTION_PREFIX + "ACTION_FAST_PAIR";
+
     public static final String EXTRA_PRIVATE_BLE_ADDRESS =
             ACTION_PREFIX + "EXTRA_PRIVATE_BLE_ADDRESS";
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java
new file mode 100644
index 0000000..d8091ba
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.fastpair.blocklist;
+
+
+/**
+ * Skeletal implementation of Blocklist
+ *
+ * <p>Controls the frequency to show the available device to users.
+ */
+public interface Blocklist {
+
+    /** Checks certain item is blocked within durationSeconds. */
+    boolean isBlocklisted(int id, int durationSeconds);
+
+    /** Updates the HalfSheet blocklist state for a given id. */
+    boolean updateState(int id, BlocklistState state);
+
+    /** Removes the HalfSheet blocklist. */
+    boolean removeBlocklist(int id);
+
+    /** Resets certain device ban state to active. */
+    void resetBlockState(int id);
+
+    /**
+     * Used for indicate what state is the blocklist item.
+     *
+     * <p>The different states have differing priorities and higher priority states will override
+     * lower one.
+     * More details and state transition diagram,
+     * see: https://docs.google.com/document/d/1wzE5CHXTkzKJY-2AltSrxOVteom2Nebc1sbjw1Tt7BQ/edit?usp=sharing&resourcekey=0-L-wUz3Hw5gZPThm5VPwHOQ
+     */
+    enum BlocklistState {
+        UNKNOWN(0),
+        ACTIVE(1),
+        DISMISSED(2),
+        PAIRING(3),
+        PAIRED(4),
+        DO_NOT_SHOW_AGAIN(5),
+        DO_NOT_SHOW_AGAIN_LONG(6);
+
+        private final int mValue;
+
+        BlocklistState(final int value) {
+            this.mValue = value;
+        }
+
+        public boolean hasHigherPriorityThan(BlocklistState otherState) {
+            return this.mValue > otherState.mValue;
+        }
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java
new file mode 100644
index 0000000..d058d58
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.fastpair.blocklist;
+
+import com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetBlocklist;
+
+/** Element in the {@link FastPairHalfSheetBlocklist} */
+public class BlocklistElement {
+    private final long mTimeStamp;
+    private final BlocklistState mState;
+
+    public BlocklistElement(BlocklistState state, long timeStamp) {
+        this.mState = state;
+        this.mTimeStamp = timeStamp;
+    }
+
+    public Long getTimeStamp() {
+        return mTimeStamp;
+    }
+
+    public BlocklistState getState() {
+        return mState;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
index 6065f99..5ce4488 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
@@ -28,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.common.ble.util.RangingUtils;
 import com.android.server.nearby.common.fastpair.IconUtils;
 import com.android.server.nearby.common.locator.Locator;
@@ -106,15 +107,6 @@
     }
 
     /**
-     * Sets the store discovery item mac address.
-     */
-    public void setMacAddress(String address) {
-        mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().setMacAddress(address).build();
-
-        mFastPairCacheManager.saveDiscoveryItem(this);
-    }
-
-    /**
      * Checks if the item is expired. Expired items are those over getItemExpirationMillis() eg. 2
      * minutes
      */
@@ -295,7 +287,8 @@
      * Returns the app name of discovery item.
      */
     @Nullable
-    private String getAppName() {
+    @VisibleForTesting
+    protected String getAppName() {
         return mStoredDiscoveryItem.getAppName();
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
index b840091..c6134f5 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
@@ -64,16 +64,6 @@
     }
 
     /**
-     * Checks if the entry can be auto deleted from the cache
-     */
-    public boolean isDeletable(Cache.ServerResponseDbItem entry) {
-        if (!entry.getExpirable()) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
      * Save discovery item into database. Discovery item is item that discovered through Ble before
      * pairing success.
      */
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java
new file mode 100644
index 0000000..146b97a
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 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.fastpair.halfsheet;
+
+
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.ACTIVE;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DISMISSED;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG;
+
+import android.util.Log;
+import android.util.LruCache;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.server.nearby.fastpair.blocklist.Blocklist;
+import com.android.server.nearby.fastpair.blocklist.BlocklistElement;
+import com.android.server.nearby.util.Clock;
+import com.android.server.nearby.util.DefaultClock;
+
+
+/**
+ * Maintains a list of half sheet id to tell whether the half sheet should be suppressed or not.
+ *
+ * <p>When user cancel half sheet, the ble address related half sheet should be in block list and
+ * after certain duration of time half sheet can show again.
+ */
+public class FastPairHalfSheetBlocklist extends LruCache<Integer, BlocklistElement>
+        implements Blocklist {
+    private static final String TAG = "HalfSheetBlocklist";
+    // Number of entries in the FastPair blocklist
+    private static final int FAST_PAIR_BLOCKLIST_CACHE_SIZE = 16;
+    // Duration between first half sheet dismiss and second half sheet shows: 2 seconds
+    private static final int FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS = 2000;
+    // The timeout to ban half sheet after user trigger the ban logic even number of time : 1 day
+    private static final int DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS = 86400000;
+    // Timeout for DISMISSED entries in the blocklist to expire : 1 min
+    private static final int FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS = 60000;
+    // The timeout for entries in the blocklist to expire : 1 day
+    private static final int STATE_EXPIRATION_MILLI_SECONDS = 86400000;
+    private long mEndTimeBanAllItems;
+    private final Clock mClock;
+
+
+    public FastPairHalfSheetBlocklist() {
+        // Reuses the size limit from notification cache.
+        // Number of entries in the FastPair blocklist
+        super(FAST_PAIR_BLOCKLIST_CACHE_SIZE);
+        mClock = new DefaultClock();
+    }
+
+    @VisibleForTesting
+    FastPairHalfSheetBlocklist(int size, Clock clock) {
+        super(size);
+        mClock = clock;
+    }
+
+    /**
+     * Checks whether need to show HalfSheet or not.
+     *
+     * <p> When the HalfSheet {@link BlocklistState} is DISMISS, there is a little cool down period
+     * to allow half sheet to reshow.
+     * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN, within durationMilliSeconds
+     * from banned start time, the function will return true
+     * otherwise it will return false if the status is expired
+     * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN_LONG, the half sheet will be
+     * baned for a longer duration.
+     *
+     * @param id {@link com.android.nearby.halfsheet.HalfSheetActivity} id
+     * @param durationMilliSeconds the time duration from item is banned to now
+     * @return whether the HalfSheet is blocked to show
+     */
+    @Override
+    public boolean isBlocklisted(int id, int durationMilliSeconds) {
+        if (shouldBanAllItem()) {
+            return true;
+        }
+        BlocklistElement entry = get(id);
+        if (entry == null) {
+            return false;
+        }
+        if (entry.getState().equals(DO_NOT_SHOW_AGAIN)) {
+            Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN");
+            return mClock.elapsedRealtime() < entry.getTimeStamp() + durationMilliSeconds;
+        }
+        if (entry.getState().equals(DO_NOT_SHOW_AGAIN_LONG)) {
+            Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN_LONG ");
+            return mClock.elapsedRealtime()
+                    < entry.getTimeStamp()
+                    + DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS;
+        }
+
+        if (entry.getState().equals(ACTIVE)) {
+            Log.d(TAG, "BlocklistState: ACTIVE");
+            return false;
+        }
+        // Get some cool down period for dismiss state
+        if (entry.getState().equals(DISMISSED)) {
+            Log.d(TAG, "BlocklistState: DISMISSED");
+            return mClock.elapsedRealtime()
+                    < entry.getTimeStamp() + FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS;
+        }
+        if (dismissStateHasExpired(entry)) {
+            Log.d(TAG, "stateHasExpired: True");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean removeBlocklist(int id) {
+        BlocklistElement oldValue = remove(id);
+        return oldValue != null;
+    }
+
+    /**
+     * Updates the HalfSheet blocklist state
+     *
+     * <p>When the new {@link BlocklistState} has higher priority then old {@link BlocklistState} or
+     * the old {@link BlocklistState} status is expired,the function will update the status.
+     *
+     * @param id HalfSheet id
+     * @param state Blocklist state
+     * @return update status successful or not
+     */
+    @Override
+    public boolean updateState(int id, BlocklistState state) {
+        BlocklistElement entry = get(id);
+        if (entry == null || state.hasHigherPriorityThan(entry.getState())
+                || dismissStateHasExpired(entry)) {
+            Log.d(TAG, "updateState: " + state);
+            put(id, new BlocklistElement(state, mClock.elapsedRealtime()));
+            return true;
+        }
+        return false;
+    }
+
+    /** Enables lower state to override the higher value state. */
+    public void forceUpdateState(int id, BlocklistState state) {
+        put(id, new BlocklistElement(state, mClock.elapsedRealtime()));
+    }
+
+    /** Resets certain device ban state to active. */
+    @Override
+    public void resetBlockState(int id) {
+        BlocklistElement entry = get(id);
+        if (entry != null) {
+            put(id, new BlocklistElement(ACTIVE, mClock.elapsedRealtime()));
+        }
+    }
+
+    /** Checks whether certain device state has expired. */
+    public boolean isStateExpired(int id) {
+        BlocklistElement entry = get(id);
+        if (entry != null) {
+            return mClock.elapsedRealtime() > entry.getTimeStamp() + STATE_EXPIRATION_MILLI_SECONDS;
+        }
+        return false;
+    }
+
+    private boolean dismissStateHasExpired(BlocklistElement entry) {
+        return mClock.elapsedRealtime()
+                > entry.getTimeStamp() + FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS;
+    }
+
+    /**
+     * Updates the end time that all half sheet will be banned.
+     */
+    void banAllItem(long banDurationTimeMillis) {
+        long endTime = mClock.elapsedRealtime() + banDurationTimeMillis;
+        if (endTime > mEndTimeBanAllItems) {
+            mEndTimeBanAllItems = endTime;
+        }
+    }
+
+    private boolean shouldBanAllItem() {
+        return mClock.elapsedRealtime() < mEndTimeBanAllItems;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
index 553d5ce..7b266a7 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
@@ -16,34 +16,51 @@
 
 package com.android.server.nearby.fastpair.halfsheet;
 
-import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
-import static com.android.server.nearby.fastpair.FastPairManager.ACTION_RESOURCES_APK;
+import static com.android.nearby.halfsheet.constants.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BINDER;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BUNDLE;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_CONTENT;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_TYPE;
+import static com.android.nearby.halfsheet.constants.Constant.FAST_PAIR_HALF_SHEET_HELP_URL;
+import static com.android.nearby.halfsheet.constants.Constant.RESULT_FAIL;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.annotation.UiThread;
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.nearby.FastPairDevice;
 import android.nearby.FastPairStatusCallback;
 import android.nearby.PairStatusMetadata;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.LruCache;
+import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.common.eventloop.Annotations;
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.common.eventloop.NamedRunnable;
+import com.android.server.nearby.common.locator.Locator;
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
 import com.android.server.nearby.fastpair.FastPairController;
+import com.android.server.nearby.fastpair.PackageUtils;
+import com.android.server.nearby.fastpair.blocklist.Blocklist;
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.util.Environment;
 
+import java.util.HashMap;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import service.proto.Cache;
 
@@ -55,20 +72,56 @@
     private static final String HALF_SHEET_CLASS_NAME =
             "com.android.nearby.halfsheet.HalfSheetActivity";
     private static final String TAG = "FPHalfSheetManager";
+    public static final String FINISHED_STATE = "FINISHED_STATE";
+    @VisibleForTesting static final String DISMISS_HALFSHEET_RUNNABLE_NAME = "DismissHalfSheet";
+    @VisibleForTesting static final String SHOW_TOAST_RUNNABLE_NAME = "SuccessPairingToast";
+
+    // The timeout to ban half sheet after user trigger the ban logic odd number of time: 5 mins
+    private static final int DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS = 300000;
+    // Number of seconds half sheet will show after the advertisement is no longer seen.
+    private static final int HALF_SHEET_TIME_OUT_SECONDS = 12;
+
+    static final int HALFSHEET_ID_SEED = "new_fast_pair_half_sheet".hashCode();
 
     private String mHalfSheetApkPkgName;
+    private boolean mIsHalfSheetForeground = false;
+    private boolean mIsActivePairing = false;
+    private Cache.ScanFastPairStoreItem mCurrentScanFastPairStoreItem = null;
     private final LocatorContextWrapper mLocatorContextWrapper;
+    private final AtomicInteger mNotificationIds = new AtomicInteger(HALFSHEET_ID_SEED);
+    private FastPairHalfSheetBlocklist mHalfSheetBlocklist;
+    // Todo: Make "16" a flag, which can be updated from the server side.
+    final LruCache<String, Integer> mModelIdMap = new LruCache<>(16);
+    HalfSheetDismissState mHalfSheetDismissState = HalfSheetDismissState.ACTIVE;
+    // Ban count map track the number of ban happens to certain model id
+    // If the model id is baned by the odd number of time it is banned for 5 mins
+    // if the model id is banned even number of time ban 24 hours.
+    private final Map<Integer, Integer> mBanCountMap = new HashMap<>();
 
     FastPairUiServiceImpl mFastPairUiService;
+    private NamedRunnable mDismissRunnable;
+
+    /**
+     * Half sheet state default is active. If user dismiss half sheet once controller will mark half
+     * sheet as dismiss state. If user dismiss half sheet twice controller will mark half sheet as
+     * ban state for certain period of time.
+     */
+    enum HalfSheetDismissState {
+        ACTIVE,
+        DISMISS,
+        BAN
+    }
 
     public FastPairHalfSheetManager(Context context) {
         this(new LocatorContextWrapper(context));
+        mHalfSheetBlocklist = new FastPairHalfSheetBlocklist();
     }
 
     @VisibleForTesting
-    FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
+    public FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
         mLocatorContextWrapper = locatorContextWrapper;
         mFastPairUiService = new FastPairUiServiceImpl();
+        mHalfSheetBlocklist = new FastPairHalfSheetBlocklist();
     }
 
     /**
@@ -76,6 +129,40 @@
      * app can't get the correct component name.
      */
     public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
+        String modelId = scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT);
+        if (modelId == null) {
+            Log.d(TAG, "model id not found");
+            return;
+        }
+
+        synchronized (mModelIdMap) {
+            if (mModelIdMap.get(modelId) == null) {
+                mModelIdMap.put(modelId, createNewHalfSheetId());
+            }
+        }
+        int halfSheetId = mModelIdMap.get(modelId);
+
+        if (!allowedToShowHalfSheet(halfSheetId)) {
+            Log.d(TAG, "Not allow to show initial Half sheet");
+            return;
+        }
+
+        // If currently half sheet UI is in the foreground,
+        // DO NOT request start-activity to avoid unnecessary memory usage
+        if (mIsHalfSheetForeground) {
+            updateForegroundHalfSheet(scanFastPairStoreItem);
+            return;
+        } else {
+            // If the half sheet is not in foreground but the system is still pairing
+            // with the same device, mark as duplicate request and skip.
+            if (mCurrentScanFastPairStoreItem != null && mIsActivePairing
+                    && mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT)
+                    .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) {
+                Log.d(TAG, "Same device is pairing.");
+                return;
+            }
+        }
+
         try {
             if (mLocatorContextWrapper != null) {
                 String packageName = getHalfSheetApkPkgName();
@@ -97,32 +184,78 @@
                                         .setComponent(new ComponentName(packageName,
                                                 HALF_SHEET_CLASS_NAME)),
                                 UserHandle.CURRENT);
+                mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE);
             }
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't resolve package that contains half sheet");
         }
+        Log.d(TAG, "show initial half sheet.");
+        mCurrentScanFastPairStoreItem = scanFastPairStoreItem;
+        mIsHalfSheetForeground = true;
+        enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS);
     }
 
     /**
-     * Shows pairing fail half sheet.
+     * Auto dismiss half sheet after timeout
      */
-    public void showPairingFailed() {
-        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
-        if (pairStatusCallback != null) {
-            Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL");
-            pairStatusCallback.onPairUpdate(new FastPairDevice.Builder().build(),
-                    new PairStatusMetadata(PairStatusMetadata.Status.FAIL));
-        } else {
-            Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
-                    + "the pairStatusCallback is null");
+    @VisibleForTesting
+    void enableAutoDismiss(String address, long timeoutDuration) {
+        if (mDismissRunnable == null
+                || !mDismissRunnable.name.equals(DISMISS_HALFSHEET_RUNNABLE_NAME)) {
+            mDismissRunnable =
+                new NamedRunnable(DISMISS_HALFSHEET_RUNNABLE_NAME) {
+                    @Override
+                    public void run() {
+                        Log.d(TAG, "Dismiss the half sheet after "
+                                + timeoutDuration + " seconds");
+                        // BMW car kit will advertise even after pairing start,
+                        // to avoid the half sheet be dismissed during active pairing,
+                        // If the half sheet is in the pairing state, disable the auto dismiss.
+                        // See b/182396106
+                        if (mIsActivePairing) {
+                            return;
+                        }
+                        mIsHalfSheetForeground = false;
+                        FastPairStatusCallback pairStatusCallback =
+                                mFastPairUiService.getPairStatusCallback();
+                        if (pairStatusCallback != null) {
+                            pairStatusCallback.onPairUpdate(new FastPairDevice.Builder()
+                                            .setBluetoothAddress(address).build(),
+                                    new PairStatusMetadata(PairStatusMetadata.Status.DISMISS));
+                        } else {
+                            Log.w(TAG, "pairStatusCallback is null,"
+                                    + " failed to enable auto dismiss ");
+                        }
+                    }
+                };
         }
+        if (Locator.get(mLocatorContextWrapper, EventLoop.class).isPosted(mDismissRunnable)) {
+            disableDismissRunnable();
+        }
+        Locator.get(mLocatorContextWrapper, EventLoop.class)
+            .postRunnableDelayed(mDismissRunnable, SECONDS.toMillis(timeoutDuration));
     }
 
-    /**
-     * Get the half sheet status whether it is foreground or dismissed
-     */
-    public boolean getHalfSheetForegroundState() {
-        return true;
+    private void updateForegroundHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
+        if (mCurrentScanFastPairStoreItem == null) {
+            return;
+        }
+        if (mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT)
+                .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) {
+            // If current address is the same, reset the timeout.
+            Log.d(TAG, "same Address device, reset the auto dismiss timeout");
+            enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS);
+        } else {
+            // If current address is different, not reset timeout
+            // wait for half sheet auto dismiss or manually dismiss to start new pair.
+            if (mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT)
+                    .equals(scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))) {
+                Log.d(TAG, "same model id device is also nearby");
+            }
+            Log.d(TAG, "showInitialHalfsheet: address changed, from "
+                    +  mCurrentScanFastPairStoreItem.getAddress()
+                    + " to " + scanFastPairStoreItem.getAddress());
+        }
     }
 
     /**
@@ -140,23 +273,187 @@
 
     /**
      * Shows pairing success info.
+     * If the half sheet is not shown, show toast to remind user.
      */
     public void showPairingSuccessHalfSheet(String address) {
-        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
-        if (pairStatusCallback != null) {
+        resetPairingStateDisableAutoDismiss();
+        if (mIsHalfSheetForeground) {
+            FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
+            if (pairStatusCallback == null) {
+                Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
+                        + "the pairStatusCallback is null");
+                return;
+            }
+            Log.d(TAG, "showPairingSuccess: pairStatusCallback not NULL");
             pairStatusCallback.onPairUpdate(
                     new FastPairDevice.Builder().setBluetoothAddress(address).build(),
                     new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS));
         } else {
-            Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
-                    + "the pairStatusCallback is null");
+            Locator.get(mLocatorContextWrapper, EventLoop.class)
+                    .postRunnable(
+                            new NamedRunnable(SHOW_TOAST_RUNNABLE_NAME) {
+                                @Override
+                                public void run() {
+                                    try {
+                                        Toast.makeText(mLocatorContextWrapper,
+                                                mLocatorContextWrapper
+                                                        .getPackageManager()
+                                                        .getResourcesForApplication(
+                                                                getHalfSheetApkPkgName())
+                                                        .getString(R.string.fast_pair_device_ready),
+                                                Toast.LENGTH_LONG).show();
+                                    } catch (PackageManager.NameNotFoundException e) {
+                                        Log.d(TAG, "showPairingSuccess fail:"
+                                                + " package name cannot be found ");
+                                        e.printStackTrace();
+                                    }
+                                }
+                            });
         }
     }
 
     /**
-     * Removes dismiss runnable.
+     * Shows pairing fail half sheet.
+     * If the half sheet is not shown, create a new half sheet to help user go to Setting
+     * to manually pair with the device.
+     */
+    public void showPairingFailed() {
+        resetPairingStateDisableAutoDismiss();
+        if (mCurrentScanFastPairStoreItem == null) {
+            return;
+        }
+        if (mIsHalfSheetForeground) {
+            FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
+            if (pairStatusCallback != null) {
+                Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL");
+                pairStatusCallback.onPairUpdate(
+                        new FastPairDevice.Builder()
+                                .setBluetoothAddress(mCurrentScanFastPairStoreItem.getAddress())
+                                .build(),
+                        new PairStatusMetadata(PairStatusMetadata.Status.FAIL));
+            } else {
+                Log.w(TAG, "FastPairHalfSheetManager failed to show fail half sheet because "
+                        + "the pairStatusCallback is null");
+            }
+        } else {
+            String packageName = getHalfSheetApkPkgName();
+            if (packageName == null) {
+                Log.e(TAG, "package name is null");
+                return;
+            }
+            Bundle bundle = new Bundle();
+            bundle.putBinder(EXTRA_BINDER, mFastPairUiService);
+            mLocatorContextWrapper
+                    .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
+                                    .putExtra(EXTRA_HALF_SHEET_INFO,
+                                            mCurrentScanFastPairStoreItem.toByteArray())
+                                    .putExtra(EXTRA_HALF_SHEET_TYPE,
+                                            DEVICE_PAIRING_FRAGMENT_TYPE)
+                                    .putExtra(EXTRA_HALF_SHEET_CONTENT, RESULT_FAIL)
+                                    .putExtra(EXTRA_BUNDLE, bundle)
+                                    .setComponent(new ComponentName(packageName,
+                                            HALF_SHEET_CLASS_NAME)),
+                            UserHandle.CURRENT);
+            Log.d(TAG, "Starts a new half sheet to showPairingFailed");
+            String modelId = mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT);
+            if (modelId == null || mModelIdMap.get(modelId) == null) {
+                Log.d(TAG, "info not enough");
+                return;
+            }
+            int halfSheetId = mModelIdMap.get(modelId);
+            mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE);
+        }
+    }
+
+    /**
+     * Removes dismiss half sheet runnable. When half sheet shows, there is timer for half sheet to
+     * dismiss. But when user is pairing, half sheet should not dismiss.
+     * So this function disable the runnable.
      */
     public void disableDismissRunnable() {
+        if (mDismissRunnable == null) {
+            return;
+        }
+        Log.d(TAG, "remove dismiss runnable");
+        Locator.get(mLocatorContextWrapper, EventLoop.class).removeRunnable(mDismissRunnable);
+    }
+
+    /**
+     * When user first click back button or click the empty space in half sheet the half sheet will
+     * be banned for certain short period of time for that device model id. When user click cancel
+     * or dismiss half sheet for the second time the half sheet related item should be added to
+     * blocklist so the half sheet will not show again to interrupt user.
+     *
+     * @param modelId half sheet display item modelId.
+     */
+    @Annotations.EventThread
+    public void dismiss(String modelId) {
+        Log.d(TAG, "HalfSheetManager report dismiss device modelId: " + modelId);
+        mIsHalfSheetForeground = false;
+        Integer halfSheetId = mModelIdMap.get(modelId);
+        if (mDismissRunnable != null
+                && Locator.get(mLocatorContextWrapper, EventLoop.class)
+                          .isPosted(mDismissRunnable)) {
+            disableDismissRunnable();
+        }
+        if (halfSheetId != null) {
+            Log.d(TAG, "id: " + halfSheetId + " half sheet is dismissed");
+            boolean isDontShowAgain =
+                    !mHalfSheetBlocklist.updateState(halfSheetId,
+                            Blocklist.BlocklistState.DISMISSED);
+            if (isDontShowAgain) {
+                if (!mBanCountMap.containsKey(halfSheetId)) {
+                    mBanCountMap.put(halfSheetId, 0);
+                }
+                int dismissCountTrack = mBanCountMap.get(halfSheetId) + 1;
+                mBanCountMap.put(halfSheetId, dismissCountTrack);
+                if (dismissCountTrack % 2 == 1) {
+                    Log.d(TAG, "id: " + halfSheetId + " half sheet is short time banned");
+                    mHalfSheetBlocklist.forceUpdateState(halfSheetId,
+                            Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN);
+                } else {
+                    Log.d(TAG, "id: " + halfSheetId +  " half sheet is long time banned");
+                    mHalfSheetBlocklist.updateState(halfSheetId,
+                            Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG);
+                }
+            }
+        }
+    }
+
+    /**
+     * Changes the half sheet ban state to active.
+     */
+    @UiThread
+    public void resetBanState(String modelId) {
+        Log.d(TAG, "HalfSheetManager reset device ban state modelId: " + modelId);
+        Integer halfSheetId = mModelIdMap.get(modelId);
+        if (halfSheetId == null) {
+            Log.d(TAG, "halfSheetId not found.");
+            return;
+        }
+        mHalfSheetBlocklist.resetBlockState(halfSheetId);
+    }
+
+    // Invokes this method to reset some states when showing the pairing result.
+    private void resetPairingStateDisableAutoDismiss() {
+        mIsActivePairing = false;
+        if (mDismissRunnable != null && Locator.get(mLocatorContextWrapper, EventLoop.class)
+                .isPosted(mDismissRunnable)) {
+            disableDismissRunnable();
+        }
+    }
+
+    /**
+     * When the device pairing finished should remove the suppression for the model id
+     * so the user canntry twice if the user want to.
+     */
+    public void reportDonePairing(int halfSheetId) {
+        mHalfSheetBlocklist.removeBlocklist(halfSheetId);
+    }
+
+    @VisibleForTesting
+    public FastPairHalfSheetBlocklist getHalfSheetBlocklist() {
+        return mHalfSheetBlocklist;
     }
 
     /**
@@ -166,9 +463,96 @@
     }
 
     /**
-     * Notify manager the pairing has finished.
+     * Notifies manager the pairing has finished.
      */
     public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {
+        mCurrentScanFastPairStoreItem = null;
+        mIsHalfSheetForeground = false;
+    }
+
+    private boolean allowedToShowHalfSheet(int halfSheetId) {
+        // Half Sheet will not show when the screen is locked so disable half sheet
+        KeyguardManager keyguardManager =
+                mLocatorContextWrapper.getSystemService(KeyguardManager.class);
+        if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+            Log.d(TAG, "device is locked");
+            return false;
+        }
+
+        // Check whether the blocklist state has expired
+        if (mHalfSheetBlocklist.isStateExpired(halfSheetId)) {
+            mHalfSheetBlocklist.removeBlocklist(halfSheetId);
+            mBanCountMap.remove(halfSheetId);
+        }
+
+        // Half Sheet will not show when the model id is banned
+        if (mHalfSheetBlocklist.isBlocklisted(
+                halfSheetId, DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)) {
+            Log.d(TAG, "id: " + halfSheetId + " is blocked");
+            return false;
+        }
+        return  !isHelpPageForeground();
+    }
+
+    /**
+    * Checks if the user already open the info page, return true to suppress half sheet.
+    * ActivityManager#getRunningTasks to get the most recent task and check the baseIntent's
+    * url to see if we should suppress half sheet.
+    */
+    private boolean isHelpPageForeground() {
+        ActivityManager activityManager =
+                mLocatorContextWrapper.getSystemService(ActivityManager.class);
+        if (activityManager == null) {
+            Log.d(TAG, "ActivityManager is null");
+            return false;
+        }
+        try {
+            List<ActivityManager.RunningTaskInfo> taskInfos = activityManager.getRunningTasks(1);
+            if (taskInfos.isEmpty()) {
+                Log.d(TAG, "Empty running tasks");
+                return false;
+            }
+            String url = taskInfos.get(0).baseIntent.getDataString();
+            Log.d(TAG, "Info page url:" + url);
+            if (FAST_PAIR_HALF_SHEET_HELP_URL.equals(url)) {
+                return true;
+            }
+        } catch (SecurityException e) {
+            Log.d(TAG, "Unable to get running tasks");
+        }
+        return false;
+    }
+
+    /** Report actively pairing when the Fast Pair starts. */
+    public void reportActivelyPairing() {
+        mIsActivePairing = true;
+    }
+
+
+    private Integer createNewHalfSheetId() {
+        return mNotificationIds.getAndIncrement();
+    }
+
+    /** Gets the half sheet status whether it is foreground or dismissed */
+    public boolean getHalfSheetForeground() {
+        return mIsHalfSheetForeground;
+    }
+
+    /** Sets whether the half sheet is at the foreground or not. */
+    public void setHalfSheetForeground(boolean state) {
+        mIsHalfSheetForeground = state;
+    }
+
+    /** Returns whether the fast pair is actively pairing . */
+    @VisibleForTesting
+    public boolean isActivePairing() {
+        return mIsActivePairing;
+    }
+
+    /** Sets fast pair to be active pairing or not, used for testing. */
+    @VisibleForTesting
+    public void setIsActivePairing(boolean isActivePairing) {
+        mIsActivePairing = isActivePairing;
     }
 
     /**
@@ -176,39 +560,12 @@
      * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
      * race condition check. Since there is no lock for mHalfSheetApkPkgName.
      */
-    String getHalfSheetApkPkgName() {
+    private String getHalfSheetApkPkgName() {
         if (mHalfSheetApkPkgName != null) {
             return mHalfSheetApkPkgName;
         }
-        List<ResolveInfo> resolveInfos = mLocatorContextWrapper
-                .getPackageManager().queryIntentActivities(
-                        new Intent(ACTION_RESOURCES_APK),
-                        PackageManager.MATCH_SYSTEM_ONLY);
-
-        // remove apps that don't live in the nearby apex
-        resolveInfos.removeIf(info ->
-                !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
-
-        if (resolveInfos.isEmpty()) {
-            // Resource APK not loaded yet, print a stack trace to see where this is called from
-            Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
-                            + " APK is installed or package manager can't resolve correctly!",
-                    new IllegalStateException());
-            return null;
-        }
-
-        if (resolveInfos.size() > 1) {
-            // multiple apps found, log a warning, but continue
-            Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
-                    + resolveInfos.stream()
-                    .map(info -> info.activityInfo.applicationInfo.packageName)
-                    .collect(Collectors.joining(", ")));
-        }
-
-        // Assume the first ResolveInfo is the one we're looking for
-        ResolveInfo info = resolveInfos.get(0);
-        mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
-        Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
+        mHalfSheetApkPkgName = PackageUtils.getHalfSheetApkPkgName(mLocatorContextWrapper);
+        Log.v(TAG, "Found halfsheet APK at: " + mHalfSheetApkPkgName);
         return mHalfSheetApkPkgName;
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
index 3bd273e..eb1fb85 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.fastpair.FastPairController;
 
 /**
@@ -93,6 +94,14 @@
     }
 
     /**
+     * Sets fastPairStatusCallback.
+     */
+    @VisibleForTesting
+    public void setFastPairStatusCallback(FastPairStatusCallback fastPairStatusCallback) {
+        mFastPairStatusCallback = fastPairStatusCallback;
+    }
+
+    /**
      * Sets function for Fast Pair controller.
      */
     public void setFastPairController(FastPairController fastPairController) {
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java
new file mode 100644
index 0000000..4260235
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+import com.android.server.nearby.fastpair.PackageUtils;
+
+/** Wrapper class for Fast Pair specific logic for notification builder. */
+public class FastPairNotificationBuilder extends Notification.Builder {
+
+    @VisibleForTesting
+    static final String NOTIFICATION_OVERRIDE_NAME_EXTRA = "android.substName";
+    final String mPackageName;
+    final Context mContext;
+    final HalfSheetResources mResources;
+
+    public FastPairNotificationBuilder(Context context, String channelId) {
+        super(context, channelId);
+        this.mContext = context;
+        this.mPackageName = PackageUtils.getHalfSheetApkPkgName(context);
+        this.mResources = new HalfSheetResources(context);
+    }
+
+    /**
+     * If the flag is enabled, all the devices notification should use "Devices" as the source name,
+     * and links/Apps uses "Nearby". If the flag is not enabled, all notifications use "Nearby" as
+     * source name.
+     */
+    public FastPairNotificationBuilder setIsDevice(boolean isDevice) {
+        Bundle extras = new Bundle();
+        String notificationOverrideName =
+                isDevice
+                        ? mResources.get().getString(R.string.common_devices)
+                        : mResources.get().getString(R.string.common_nearby_title);
+        extras.putString(NOTIFICATION_OVERRIDE_NAME_EXTRA, notificationOverrideName);
+        addExtras(extras);
+        return this;
+    }
+
+    /** Set the "ticker" text which is sent to accessibility services. */
+    public FastPairNotificationBuilder setTickerForAccessibility(String tickerText) {
+        // On Lollipop and above, setTicker() tells Accessibility what to say about the notification
+        // (e.g. this is what gets announced when a HUN appears).
+        setTicker(tickerText);
+        return this;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
index b1ae573..c74249c 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
@@ -16,51 +16,166 @@
 
 package com.android.server.nearby.fastpair.notification;
 
+import static com.android.server.nearby.fastpair.Constant.TAG;
 
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
 import android.content.Context;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.fastpair.HalfSheetResources;
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
 
+import com.google.common.base.Objects;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import java.util.concurrent.TimeUnit;
+
 /**
  * Responsible for show notification logic.
  */
 public class FastPairNotificationManager {
 
-    /**
-     * FastPair notification manager that handle notification ui for fast pair.
-     */
-    public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon,
-            int notificationId) {
-    }
-    /**
-     * FastPair notification manager that handle notification ui for fast pair.
-     */
-    public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon) {
+    private static int sInstanceId = 0;
+    // Notification channel group ID  for Devices notification channels.
+    private static final String DEVICES_CHANNEL_GROUP_ID = "DEVICES_CHANNEL_GROUP_ID";
+    // These channels are rebranded string because they are migrated from different channel ID they
+    // should not be changed.
+    // Channel ID for channel "Devices within reach".
+    static final String DEVICES_WITHIN_REACH_CHANNEL_ID = "DEVICES_WITHIN_REACH_REBRANDED";
+    // Channel ID for channel "Devices".
+    static final String DEVICES_CHANNEL_ID = "DEVICES_REBRANDED";
+    // Channel ID for channel "Devices with your account".
+    public static final String DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_ID = "DEVICES_WITH_YOUR_ACCOUNT";
 
+    // Default channel importance for channel "Devices within reach".
+    private static final int DEFAULT_DEVICES_WITHIN_REACH_CHANNEL_IMPORTANCE =
+            NotificationManager.IMPORTANCE_HIGH;
+    // Default channel importance for channel "Devices".
+    private static final int DEFAULT_DEVICES_CHANNEL_IMPORTANCE =
+            NotificationManager.IMPORTANCE_LOW;
+    // Default channel importance for channel "Devices with your account".
+    private static final int DEFAULT_DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_IMPORTANCE =
+            NotificationManager.IMPORTANCE_MIN;
+
+    /** Fixed notification ID that won't duplicated with {@code notificationId}. */
+    private static final int MAGIC_PAIR_NOTIFICATION_ID = "magic_pair_notification_id".hashCode();
+    /** Fixed notification ID that won't duplicated with {@code mNotificationId}. */
+    @VisibleForTesting
+    static final int PAIR_SUCCESS_NOTIFICATION_ID = MAGIC_PAIR_NOTIFICATION_ID - 1;
+    /** Fixed notification ID for showing the pairing failure notification. */
+    @VisibleForTesting static final int PAIR_FAILURE_NOTIFICATION_ID =
+            MAGIC_PAIR_NOTIFICATION_ID - 3;
+
+    /**
+     * The amount of delay enforced between notifications. The system only allows 10 notifications /
+     * second, but delays in the binder IPC can cause overlap.
+     */
+    private static final long MIN_NOTIFICATION_DELAY_MILLIS = 300;
+
+    // To avoid a (really unlikely) race where the user pairs and succeeds quickly more than once,
+    // use a unique ID per session, so we can delay cancellation without worrying.
+    // This is for connecting related notifications only. Discovery notification will use item id
+    // as notification id.
+    @VisibleForTesting
+    final int mNotificationId;
+    private HalfSheetResources mResources;
+    private final FastPairNotifications mNotifications;
+    private boolean mDiscoveryNotificationEnable = true;
+    // A static cache that remembers all recently shown notifications. We use this to throttle
+    // ourselves from showing notifications too rapidly. If we attempt to show a notification faster
+    // than once every 100ms, the later notifications will be dropped and we'll show stale state.
+    // Maps from Key -> Uptime Millis
+    private final Cache<Key, Long> mNotificationCache =
+            CacheBuilder.newBuilder()
+                    .maximumSize(100)
+                    .expireAfterWrite(MIN_NOTIFICATION_DELAY_MILLIS, TimeUnit.MILLISECONDS)
+                    .build();
+    private NotificationManager mNotificationManager;
+
+    /**
+     * FastPair notification manager that handle notification ui for fast pair.
+     */
+    @VisibleForTesting
+    public FastPairNotificationManager(Context context, int notificationId,
+            NotificationManager notificationManager, HalfSheetResources resources) {
+        mNotificationId = notificationId;
+        mNotificationManager = notificationManager;
+        mResources = resources;
+        mNotifications = new FastPairNotifications(context, mResources);
+
+        configureDevicesNotificationChannels();
+    }
+
+    /**
+     * FastPair notification manager that handle notification ui for fast pair.
+     */
+    public FastPairNotificationManager(Context context, int notificationId) {
+        this(context, notificationId, context.getSystemService(NotificationManager.class),
+                new HalfSheetResources(context));
+    }
+
+    /**
+     * FastPair notification manager that handle notification ui for fast pair.
+     */
+    public FastPairNotificationManager(Context context) {
+        this(context, /* notificationId= */ MAGIC_PAIR_NOTIFICATION_ID + sInstanceId);
+
+        sInstanceId++;
+    }
+
+    /**
+     *  Shows the notification when found saved device. A notification will be like
+     *  "Your saved device is available."
+     *  This uses item id as notification Id. This should be disabled when connecting starts.
+     */
+    public void showDiscoveryNotification(DiscoveryItem item, byte[] accountKey) {
+        if (mDiscoveryNotificationEnable) {
+            Log.v(TAG, "the discovery notification is disabled");
+            return;
+        }
+
+        show(item.getId().hashCode(), mNotifications.discoveryNotification(item, accountKey));
     }
 
     /**
      * Shows pairing in progress notification.
      */
-    public void showConnectingNotification() {}
+    public void showConnectingNotification(DiscoveryItem item) {
+        disableShowDiscoveryNotification();
+        cancel(PAIR_FAILURE_NOTIFICATION_ID);
+        show(mNotificationId, mNotifications.progressNotification(item));
+    }
 
     /**
-     * Shows success notification
+     * Shows when Fast Pair successfully pairs the headset.
      */
     public void showPairingSucceededNotification(
-            @Nullable String companionApp,
+            DiscoveryItem item,
             int batteryLevel,
-            @Nullable String deviceName,
-            String address) {
-
+            @Nullable String deviceName) {
+        enableShowDiscoveryNotification();
+        cancel(mNotificationId);
+        show(PAIR_SUCCESS_NOTIFICATION_ID,
+                mNotifications
+                        .pairingSucceededNotification(
+                                batteryLevel, deviceName, item.getTitle(), item));
     }
 
     /**
      * Shows failed notification.
      */
-    public void showPairingFailedNotification(byte[] accountKey) {
-
+    public synchronized void showPairingFailedNotification(DiscoveryItem item, byte[] accountKey) {
+        enableShowDiscoveryNotification();
+        cancel(mNotificationId);
+        show(PAIR_FAILURE_NOTIFICATION_ID,
+                mNotifications.showPairingFailedNotification(item, accountKey));
     }
 
     /**
@@ -68,4 +183,98 @@
      */
     public void notifyPairingProcessDone(boolean success, boolean forceNotify,
             String privateAddress, String publicAddress) {}
+
+    /** Enables the discovery notification when pairing is in progress */
+    public void enableShowDiscoveryNotification() {
+        Log.v(TAG, "enabling discovery notification");
+        mDiscoveryNotificationEnable = true;
+    }
+
+    /** Disables the discovery notification when pairing is in progress */
+    public synchronized void disableShowDiscoveryNotification() {
+        Log.v(TAG, "disabling discovery notification");
+        mDiscoveryNotificationEnable = false;
+    }
+
+    private void show(int id, Notification notification) {
+        mNotificationManager.notify(id, notification);
+    }
+
+    /**
+     * Configures devices related notification channels, including "Devices" and "Devices within
+     * reach" channels.
+     */
+    private void configureDevicesNotificationChannels() {
+        mNotificationManager.createNotificationChannelGroup(
+                new NotificationChannelGroup(
+                        DEVICES_CHANNEL_GROUP_ID,
+                        mResources.get().getString(R.string.common_devices)));
+        mNotificationManager.createNotificationChannel(
+                createNotificationChannel(
+                        DEVICES_WITHIN_REACH_CHANNEL_ID,
+                        mResources.get().getString(R.string.devices_within_reach_channel_name),
+                        DEFAULT_DEVICES_WITHIN_REACH_CHANNEL_IMPORTANCE,
+                        DEVICES_CHANNEL_GROUP_ID));
+        mNotificationManager.createNotificationChannel(
+                createNotificationChannel(
+                        DEVICES_CHANNEL_ID,
+                        mResources.get().getString(R.string.common_devices),
+                        DEFAULT_DEVICES_CHANNEL_IMPORTANCE,
+                        DEVICES_CHANNEL_GROUP_ID));
+        mNotificationManager.createNotificationChannel(
+                createNotificationChannel(
+                        DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_ID,
+                        mResources.get().getString(R.string.devices_with_your_account_channel_name),
+                        DEFAULT_DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_IMPORTANCE,
+                        DEVICES_CHANNEL_GROUP_ID));
+    }
+
+    private NotificationChannel createNotificationChannel(
+            String channelId, String channelName, int channelImportance, String channelGroupId) {
+        NotificationChannel channel =
+                new NotificationChannel(channelId, channelName, channelImportance);
+        channel.setGroup(channelGroupId);
+        if (channelImportance >= NotificationManager.IMPORTANCE_HIGH) {
+            channel.setSound(/* sound= */ null, /* audioAttributes= */ null);
+            // Disable vibration. Otherwise, the silent sound triggers a vibration if your
+            // ring volume is set to vibrate (aka turned down all the way).
+            channel.enableVibration(false);
+        }
+
+        return channel;
+    }
+
+    /** Cancel a previously shown notification. */
+    public void cancel(int id) {
+        try {
+            mNotificationManager.cancel(id);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Failed to cancel notification " + id, e);
+        }
+        mNotificationCache.invalidate(new Key(id));
+    }
+
+    private static final class Key {
+        @Nullable final String mTag;
+        final int mId;
+
+        Key(int id) {
+            this.mTag = null;
+            this.mId = id;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o instanceof Key) {
+                Key that = (Key) o;
+                return Objects.equal(mTag, that.mTag) && (mId == that.mId);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mTag == null ? 0 : mTag, mId);
+        }
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java
new file mode 100644
index 0000000..3b4eef8
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR;
+import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_FAST_PAIR_SECRET;
+import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_ITEM_ID;
+import static com.android.server.nearby.fastpair.notification.FastPairNotificationManager.DEVICES_WITHIN_REACH_CHANNEL_ID;
+
+import static com.google.common.io.BaseEncoding.base16;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.common.fastpair.IconUtils;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+import service.proto.Cache;
+
+/**
+ * Collection of utilities to create {@link Notification} objects that are displayed through {@link
+ * FastPairNotificationManager}.
+ */
+public class FastPairNotifications {
+
+    private final Context mContext;
+    private final HalfSheetResources mResources;
+    /**
+     * Note: Idea copied from Google.
+     *
+     * <p>Request code used for notification pending intents (executed on tap, dismiss).
+     *
+     * <p>Android only keeps one PendingIntent instance if it thinks multiple pending intents match.
+     * As comparing PendingIntents/Intents does not inspect the data in the extras, multiple pending
+     * intents can conflict. This can have surprising consequences (see b/68702692#comment8).
+     *
+     * <p>We also need to avoid conflicts with notifications started by an earlier launch of the app
+     * so use the truncated uptime of when the class was instantiated. The uptime will only overflow
+     * every ~50 days, and even then chances of conflict will be rare.
+     */
+    private static int sRequestCode = (int) SystemClock.elapsedRealtime();
+
+    public FastPairNotifications(Context context, HalfSheetResources resources) {
+        this.mContext = context;
+        this.mResources = resources;
+    }
+
+    /**
+     * Creates the initial "Your saved device is available" notification when subsequent pairing
+     * is available.
+     * @param item discovered item which contains title and item id
+     * @param accountKey used for generating intent for pairing
+     */
+    public Notification discoveryNotification(DiscoveryItem item, byte[] accountKey) {
+        Notification.Builder builder =
+                newBaseBuilder(item)
+                        .setContentTitle(mResources.getString(R.string.fast_pair_your_device))
+                        .setContentText(item.getTitle())
+                        .setContentIntent(getPairIntent(item.getCopyOfStoredItem(), accountKey))
+                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
+                        .setAutoCancel(false);
+        return builder.build();
+    }
+
+    /**
+     * Creates the in progress "Connecting" notification when the device and headset are paring.
+     */
+    public Notification progressNotification(DiscoveryItem item) {
+        String summary = mResources.getString(R.string.common_connecting);
+        Notification.Builder builder =
+                newBaseBuilder(item)
+                        .setTickerForAccessibility(summary)
+                        .setCategory(Notification.CATEGORY_PROGRESS)
+                        .setContentTitle(mResources.getString(R.string.fast_pair_your_device))
+                        .setContentText(summary)
+                        // Intermediate progress bar.
+                        .setProgress(0, 0, true)
+                        // Tapping does not dismiss this.
+                        .setAutoCancel(false);
+
+        return builder.build();
+    }
+
+    /**
+     * Creates paring failed notification.
+     */
+    public Notification showPairingFailedNotification(DiscoveryItem item, byte[] accountKey) {
+        String couldNotPair = mResources.getString(R.string.fast_pair_unable_to_connect);
+        String notificationContent;
+        if (accountKey != null) {
+            notificationContent = mResources.getString(
+                    R.string.fast_pair_turn_on_bt_device_pairing_mode);
+        } else {
+            notificationContent =
+                    mResources.getString(R.string.fast_pair_unable_to_connect_description);
+        }
+        Notification.Builder builder =
+                newBaseBuilder(item)
+                        .setTickerForAccessibility(couldNotPair)
+                        .setCategory(Notification.CATEGORY_ERROR)
+                        .setContentTitle(couldNotPair)
+                        .setContentText(notificationContent)
+                        .setContentIntent(getBluetoothSettingsIntent())
+                        // Dismissing completes the attempt.
+                        .setDeleteIntent(getBluetoothSettingsIntent());
+        return builder.build();
+
+    }
+
+    /**
+     * Creates paring successfully notification.
+     */
+    public Notification pairingSucceededNotification(
+            int batteryLevel,
+            @Nullable String deviceName,
+            String modelName,
+            DiscoveryItem item) {
+        final String contentText;
+        StringBuilder contentTextBuilder = new StringBuilder();
+        contentTextBuilder.append(modelName);
+        if (batteryLevel >= 0 && batteryLevel <= 100) {
+            contentTextBuilder
+                    .append("\n")
+                    .append(mResources.getString(R.string.common_battery_level, batteryLevel));
+        }
+        String pairingComplete =
+                deviceName == null
+                        ? mResources.getString(R.string.fast_pair_device_ready)
+                        : mResources.getString(
+                                R.string.fast_pair_device_ready_with_device_name, deviceName);
+        contentText = contentTextBuilder.toString();
+        Notification.Builder builder =
+                newBaseBuilder(item)
+                        .setTickerForAccessibility(pairingComplete)
+                        .setCategory(Notification.CATEGORY_STATUS)
+                        .setContentTitle(pairingComplete)
+                        .setContentText(contentText);
+
+        return builder.build();
+    }
+
+    private PendingIntent getPairIntent(Cache.StoredDiscoveryItem item, byte[] accountKey) {
+        Intent intent =
+                new Intent(ACTION_FAST_PAIR)
+                        .putExtra(EXTRA_ITEM_ID, item.getId())
+                        // Encode account key as a string instead of bytes so that it can be passed
+                        // to the string representation of the intent.
+                        .putExtra(EXTRA_FAST_PAIR_SECRET, base16().encode(accountKey))
+                        .setPackage(mContext.getPackageName());
+        return PendingIntent.getBroadcast(mContext, sRequestCode++, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+    }
+
+    private PendingIntent getBluetoothSettingsIntent() {
+        Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
+        return PendingIntent.getActivity(mContext, sRequestCode++, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    private LargeHeadsUpNotificationBuilder newBaseBuilder(DiscoveryItem item) {
+        LargeHeadsUpNotificationBuilder builder =
+                (LargeHeadsUpNotificationBuilder)
+                        (new LargeHeadsUpNotificationBuilder(
+                                mContext,
+                                DEVICES_WITHIN_REACH_CHANNEL_ID,
+                                /* largeIcon= */ true)
+                                .setIsDevice(true)
+                                // Tapping does not dismiss this.
+                                .setSmallIcon(Icon.createWithResource(
+                                        mResources.getResourcesContext(),
+                                        R.drawable.quantum_ic_devices_other_vd_theme_24)))
+                                .setLargeIcon(IconUtils.addWhiteCircleBackground(
+                                        mResources.getResourcesContext(), item.getIcon()))
+                                // Dismissible.
+                                .setOngoing(false)
+                                // Timestamp is not relevant, hide it.
+                                .setShowWhen(false)
+                                .setColor(mResources.getColor(R.color.discovery_activity_accent))
+                                .setLocalOnly(true)
+                                // don't show these notifications on wear devices
+                                .setAutoCancel(true);
+
+        return builder;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java
new file mode 100644
index 0000000..ec41d76
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.nearby.halfsheet.R;
+
+/** Wrapper class for creating larger heads up notifications. */
+public class LargeHeadsUpNotificationBuilder extends FastPairNotificationBuilder {
+    private final boolean mLargeIcon;
+    private final RemoteViews mNotification;
+    private final RemoteViews mNotificationCollapsed;
+
+    @Nullable private Runnable mLargeIconAction;
+    @Nullable private Runnable mProgressAction;
+
+    public LargeHeadsUpNotificationBuilder(Context context, String channelId, boolean largeIcon) {
+        super(context, channelId);
+
+        this.mLargeIcon = largeIcon;
+        this.mNotification = getRemoteViews(R.layout.fast_pair_heads_up_notification);
+        this.mNotificationCollapsed = getRemoteViews(R.layout.fast_pair_heads_up_notification);
+
+        if (mNotification != null) {
+            mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.GONE);
+        }
+    }
+
+    /**
+     * Create a new RemoteViews object that will display the views contained
+     * fast_pair_heads_up_notification layout.
+     */
+    @Nullable
+    public RemoteViews getRemoteViews(@LayoutRes int layoutId) {
+        return new RemoteViews(mPackageName, layoutId);
+    }
+
+    @Override
+    public Notification.Builder setContentTitle(@Nullable CharSequence title) {
+        if (mNotification != null) {
+            mNotification.setTextViewText(android.R.id.title, title);
+        }
+        if (mNotificationCollapsed != null) {
+            mNotificationCollapsed.setTextViewText(android.R.id.title, title);
+            // Collapsed mode does not need additional lines.
+            mNotificationCollapsed.setInt(android.R.id.title, "setMaxLines", 1);
+        }
+        return super.setContentTitle(title);
+    }
+
+    @Override
+    public Notification.Builder setContentText(@Nullable CharSequence text) {
+        if (mNotification != null) {
+            mNotification.setTextViewText(android.R.id.text1, text);
+        }
+        if (mNotificationCollapsed != null) {
+            mNotificationCollapsed.setTextViewText(android.R.id.text1, text);
+            // Collapsed mode does not need additional lines.
+            mNotificationCollapsed.setInt(android.R.id.text1, "setMaxLines", 1);
+        }
+
+        return super.setContentText(text);
+    }
+
+    @Override
+    public Notification.Builder setSubText(@Nullable CharSequence subText) {
+        if (mNotification != null) {
+            mNotification.setTextViewText(android.R.id.text2, subText);
+        }
+        if (mNotificationCollapsed != null) {
+            mNotificationCollapsed.setTextViewText(android.R.id.text2, subText);
+        }
+        return super.setSubText(subText);
+    }
+
+    @Override
+    public Notification.Builder setLargeIcon(@Nullable Bitmap bitmap) {
+        RemoteViews image =
+                getRemoteViews(
+                        useLargeIcon()
+                                ? R.layout.fast_pair_heads_up_notification_large_image
+                                : R.layout.fast_pair_heads_up_notification_small_image);
+        if (image == null) {
+            return super.setLargeIcon(bitmap);
+        }
+        image.setImageViewBitmap(android.R.id.icon, bitmap);
+
+        if (mNotification != null) {
+            mNotification.removeAllViews(android.R.id.icon1);
+            mNotification.addView(android.R.id.icon1, image);
+        }
+        if (mNotificationCollapsed != null) {
+            mNotificationCollapsed.removeAllViews(android.R.id.icon1);
+            mNotificationCollapsed.addView(android.R.id.icon1, image);
+            // In Android S, if super.setLargeIcon() is called, there will be an extra icon on
+            // top-right.
+            // But we still need to store this setting for the default UI when something wrong.
+            mLargeIconAction = () -> super.setLargeIcon(bitmap);
+            return this;
+        }
+        return super.setLargeIcon(bitmap);
+    }
+
+    @Override
+    public Notification.Builder setProgress(int max, int progress, boolean indeterminate) {
+        if (mNotification != null) {
+            mNotification.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE);
+            mNotification.setProgressBar(android.R.id.progress, max, progress, indeterminate);
+        }
+        if (mNotificationCollapsed != null) {
+            mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE);
+            mNotificationCollapsed.setProgressBar(android.R.id.progress, max, progress,
+                    indeterminate);
+            // In Android S, if super.setProgress() is called, there will be an extra progress bar.
+            // But we still need to store this setting for the default UI when something wrong.
+            mProgressAction = () -> super.setProgress(max, progress, indeterminate);
+            return this;
+        }
+        return super.setProgress(max, progress, indeterminate);
+    }
+
+    @Override
+    public Notification build() {
+        if (mNotification != null) {
+            boolean buildSuccess = false;
+            try {
+                // Attempt to apply the remote views. This verifies that all of the resources are
+                // correctly available.
+                // If it fails, fall back to a non-custom notification.
+                mNotification.apply(mContext, new LinearLayout(mContext));
+                if (mNotificationCollapsed != null) {
+                    mNotificationCollapsed.apply(mContext, new LinearLayout(mContext));
+                }
+                buildSuccess = true;
+            } catch (Resources.NotFoundException e) {
+                Log.w(TAG, "Failed to build notification, not setting custom view.", e);
+            }
+
+            if (buildSuccess) {
+                if (mNotificationCollapsed != null) {
+                    setStyle(new Notification.DecoratedCustomViewStyle());
+                    setCustomContentView(mNotificationCollapsed);
+                    setCustomBigContentView(mNotification);
+                } else {
+                    setCustomHeadsUpContentView(mNotification);
+                }
+            } else {
+                if (mLargeIconAction != null) {
+                    mLargeIconAction.run();
+                }
+                if (mProgressAction != null) {
+                    mProgressAction.run();
+                }
+            }
+        }
+        return super.build();
+    }
+
+    private boolean useLargeIcon() {
+        return mLargeIcon;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
index c95f74f..e56f1ea 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
@@ -63,9 +63,10 @@
     public void onPairingStarted() {
         super.onPairingStarted();
         // Half sheet is not in the foreground reshow half sheet, also avoid showing HalfSheet on TV
-        if (!mFastPairHalfSheetManager.getHalfSheetForegroundState()) {
+        if (!mFastPairHalfSheetManager.getHalfSheetForeground()) {
             mFastPairHalfSheetManager.showPairingHalfSheet(mItemResurface);
         }
+        mFastPairHalfSheetManager.reportActivelyPairing();
         mFastPairHalfSheetManager.disableDismissRunnable();
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
index d469c45..5317673 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
@@ -20,7 +20,6 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
 import android.content.Context;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
@@ -68,7 +67,7 @@
     @Override
     public void onReadyToPair() {
         super.onReadyToPair();
-        mFastPairNotificationManager.showConnectingNotification();
+        mFastPairNotificationManager.showConnectingNotification(mItem);
     }
 
     @Override
@@ -79,32 +78,24 @@
             String address) {
         String deviceName = super.onPairedCallbackCalled(connection, accountKey, footprints,
                 address);
-
         int batteryLevel = -1;
 
         BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
         BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
-        if (bluetoothAdapter != null) {
-            // Need to check battery level here set that to -1 for now
-            batteryLevel = -1;
+        if (address != null && bluetoothAdapter != null) {
+            batteryLevel = bluetoothAdapter.getRemoteDevice(address).getBatteryLevel();
         } else {
-            Log.v(
-                    "NotificationPairingProgressHandler",
-                    "onPairedCallbackCalled getBatteryLevel failed,"
-                            + " adapter is null");
+            Log.v(TAG, "onPairedCallbackCalled getBatteryLevel failed");
         }
-        mFastPairNotificationManager.showPairingSucceededNotification(
-                !TextUtils.isEmpty(mCompanionApp) ? mCompanionApp : null,
-                batteryLevel,
-                deviceName,
-                address);
+        mFastPairNotificationManager
+                .showPairingSucceededNotification(mItem, batteryLevel, deviceName);
         return deviceName;
     }
 
     @Override
     public void onPairingFailed(Throwable throwable) {
         super.onPairingFailed(throwable);
-        mFastPairNotificationManager.showPairingFailedNotification(mAccountKey);
+        mFastPairNotificationManager.showPairingFailedNotification(mItem, mAccountKey);
         mFastPairNotificationManager.notifyPairingProcessDone(
                 /* success= */ false,
                 /* forceNotify= */ false,
@@ -115,6 +106,19 @@
     @Override
     public void onPairingSuccess(String address) {
         super.onPairingSuccess(address);
+        int batteryLevel = -1;
+
+        BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
+        String deviceName = null;
+        if (address != null && bluetoothAdapter != null) {
+            deviceName = bluetoothAdapter.getRemoteDevice(address).getName();
+            batteryLevel = bluetoothAdapter.getRemoteDevice(address).getBatteryLevel();
+        } else {
+            Log.v(TAG, "onPairedCallbackCalled getBatteryLevel failed");
+        }
+        mFastPairNotificationManager
+                .showPairingSucceededNotification(mItem, batteryLevel, deviceName);
         mFastPairNotificationManager.notifyPairingProcessDone(
                 /* success= */ true,
                 /* forceNotify= */ false,
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
index ccd7e5e..6f2dc40 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
 import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
@@ -34,6 +35,7 @@
 
 /** Base class for pairing progress handler. */
 public abstract class PairingProgressHandlerBase {
+    protected static final String TAG = "FPPairingHandler";
     protected final Context mContext;
     protected final DiscoveryItem mItem;
     @Nullable
@@ -48,7 +50,6 @@
         this.mItem = item;
     }
 
-
     /**
      * Pairing progress init function.
      */
@@ -74,59 +75,55 @@
                     new HalfSheetPairingProgressHandler(context, item, companionApp, accountKey);
         }
 
-
-        Log.v("PairingHandler",
-                "PairingProgressHandler:Create "
-                        + item.getMacAddress() + " for pairing");
+        Log.v(TAG, "PairingProgressHandler:Create " + item.getMacAddress() + " for pairing");
         return pairingProgressHandlerBase;
     }
 
-
     /**
      * Function calls when pairing start.
      */
     public void onPairingStarted() {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingStarted");
+        Log.v(TAG, "PairingProgressHandler:onPairingStarted");
     }
 
     /**
      * Waits for screen to unlock.
      */
     public void onWaitForScreenUnlock() {
-        Log.v("PairingHandler", "PairingProgressHandler:onWaitForScreenUnlock");
+        Log.v(TAG, "PairingProgressHandler:onWaitForScreenUnlock");
     }
 
     /**
      * Function calls when screen unlock.
      */
     public void onScreenUnlocked() {
-        Log.v("PairingHandler", "PairingProgressHandler:onScreenUnlocked");
+        Log.v(TAG, "PairingProgressHandler:onScreenUnlocked");
     }
 
     /**
      * Calls when the handler is ready to pair.
      */
     public void onReadyToPair() {
-        Log.v("PairingHandler", "PairingProgressHandler:onReadyToPair");
+        Log.v(TAG, "PairingProgressHandler:onReadyToPair");
     }
 
     /**
      * Helps to set up pairing preference.
      */
     public void onSetupPreferencesBuilder(Preferences.Builder builder) {
-        Log.v("PairingHandler", "PairingProgressHandler:onSetupPreferencesBuilder");
+        Log.v(TAG, "PairingProgressHandler:onSetupPreferencesBuilder");
     }
 
     /**
      * Calls when pairing setup complete.
      */
     public void onPairingSetupCompleted() {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingSetupCompleted");
+        Log.v(TAG, "PairingProgressHandler:onPairingSetupCompleted");
     }
 
     /** Called while pairing if needs to handle the passkey confirmation by Ui. */
     public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) {
-        Log.v("PairingHandler", "PairingProgressHandler:onHandlePasskeyConfirmation");
+        Log.v(TAG, "PairingProgressHandler:onHandlePasskeyConfirmation");
     }
 
     /**
@@ -148,7 +145,7 @@
             byte[] accountKey,
             FootprintsDeviceManager footprints,
             String address) {
-        Log.v("PairingHandler",
+        Log.v(TAG,
                 "PairingProgressHandler:onPairedCallbackCalled with address: "
                         + address);
 
@@ -165,7 +162,7 @@
     public byte[] getKeyForLocalCache(
             byte[] accountKey, FastPairConnection connection,
             FastPairConnection.SharedSecret sharedSecret) {
-        Log.v("PairingHandler", "PairingProgressHandler:getKeyForLocalCache");
+        Log.v(TAG, "PairingProgressHandler:getKeyForLocalCache");
         return accountKey != null ? accountKey : connection.getExistingAccountKey();
     }
 
@@ -173,25 +170,26 @@
      * Function handles pairing fail.
      */
     public void onPairingFailed(Throwable throwable) {
-        Log.w("PairingHandler", "PairingProgressHandler:onPairingFailed");
+        Log.w(TAG, "PairingProgressHandler:onPairingFailed");
     }
 
     /**
      * Function handles pairing success.
      */
     public void onPairingSuccess(String address) {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingSuccess with address: "
+        Log.v(TAG, "PairingProgressHandler:onPairingSuccess with address: "
                 + maskBluetoothAddress(address));
     }
 
-    private static void optInFootprintsForInitialPairing(
+    @VisibleForTesting
+    static void optInFootprintsForInitialPairing(
             FootprintsDeviceManager footprints,
             DiscoveryItem item,
             byte[] accountKey,
             @Nullable byte[] existingAccountKey) {
         if (isThroughFastPair2InitialPairing(item, accountKey) && existingAccountKey == null) {
             // enable the save to footprint
-            Log.v("PairingHandler", "footprint should call opt in here");
+            Log.v(TAG, "footprint should call opt in here");
         }
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/injector/Injector.java b/nearby/service/java/com/android/server/nearby/injector/Injector.java
index 57784a9..3152ee6 100644
--- a/nearby/service/java/com/android/server/nearby/injector/Injector.java
+++ b/nearby/service/java/com/android/server/nearby/injector/Injector.java
@@ -18,6 +18,7 @@
 
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
+import android.hardware.location.ContextHubManager;
 
 /**
  * Nearby dependency injector. To be used for accessing various Nearby class instances and as a
@@ -29,7 +30,7 @@
     BluetoothAdapter getBluetoothAdapter();
 
     /** Get the ContextHubManagerAdapter for ChreDiscoveryProvider to scan. */
-    ContextHubManagerAdapter getContextHubManagerAdapter();
+    ContextHubManager getContextHubManager();
 
     /** Get the AppOpsManager to control access. */
     AppOpsManager getAppOpsManager();
diff --git a/nearby/service/java/com/android/server/nearby/presence/Advertisement.java b/nearby/service/java/com/android/server/nearby/presence/Advertisement.java
new file mode 100644
index 0000000..d42f6c7
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/Advertisement.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import android.annotation.Nullable;
+import android.nearby.BroadcastRequest;
+import android.nearby.PresenceCredential;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A Nearby Presence advertisement to be advertised. */
+public abstract class Advertisement {
+
+    @BroadcastRequest.BroadcastVersion
+    int mVersion = BroadcastRequest.PRESENCE_VERSION_UNKNOWN;
+    int mLength;
+    @PresenceCredential.IdentityType int mIdentityType;
+    byte[] mIdentity;
+    byte[] mSalt;
+    List<Integer> mActions;
+
+    /** Serialize an {@link Advertisement} object into bytes. */
+    @Nullable
+    public byte[] toBytes() {
+        return new byte[0];
+    }
+
+    /** Returns the length of the advertisement. */
+    public int getLength() {
+        return mLength;
+    }
+
+    /** Returns the version in the advertisement. */
+    @BroadcastRequest.BroadcastVersion
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /** Returns the identity type in the advertisement. */
+    @PresenceCredential.IdentityType
+    public int getIdentityType() {
+        return mIdentityType;
+    }
+
+    /** Returns the identity bytes in the advertisement. */
+    public byte[] getIdentity() {
+        return mIdentity.clone();
+    }
+
+    /** Returns the salt of the advertisement. */
+    public byte[] getSalt() {
+        return mSalt.clone();
+    }
+
+    /** Returns the actions in the advertisement. */
+    public List<Integer> getActions() {
+        return new ArrayList<>(mActions);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/DataElementHeader.java b/nearby/service/java/com/android/server/nearby/presence/DataElementHeader.java
new file mode 100644
index 0000000..ae4a728
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/DataElementHeader.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import android.annotation.Nullable;
+import android.nearby.BroadcastRequest;
+import android.nearby.DataElement;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Represents a data element header in Nearby Presence.
+ * Each header has 3 parts: tag, length and style.
+ * Tag: 1 bit (MSB at each byte). 1 for extending, which means there will be more bytes after
+ * the current one for the header.
+ * Length: The total length of a Data Element field. Length is up to 127 and is limited within
+ * the entire first byte in the header. (7 bits, MSB is the tag).
+ * Type: Represents {@link DataElement.DataType}. There is no limit for the type number.
+ *
+ * @hide
+ */
+public class DataElementHeader {
+    // Each Data reserved MSB for tag.
+    static final int TAG_BITMASK = 0b10000000;
+    static final int TAG_OFFSET = 7;
+
+    // If the header is only 1 byte, it has the format: 0b0LLLTTTT. (L for length, T for type.)
+    static final int SINGLE_AVAILABLE_LENGTH_BIT = 3;
+    static final int SINGLE_AVAILABLE_TYPE_BIT = 4;
+    static final int SINGLE_LENGTH_BITMASK = 0b01110000;
+    static final int SINGLE_LENGTH_OFFSET = SINGLE_AVAILABLE_TYPE_BIT;
+    static final int SINGLE_TYPE_BITMASK = 0b00001111;
+
+    // If there are multiple data element headers.
+    // First byte is always the length.
+    static final int MULTIPLE_LENGTH_BYTE = 1;
+    // Each byte reserves MSB for tag.
+    static final int MULTIPLE_BITMASK = 0b01111111;
+
+    @BroadcastRequest.BroadcastVersion
+    private final int mVersion;
+    @DataElement.DataType
+    private final int mDataType;
+    private final int mDataLength;
+
+    DataElementHeader(@BroadcastRequest.BroadcastVersion int version,
+            @DataElement.DataType int dataType, int dataLength) {
+        Preconditions.checkArgument(version == BroadcastRequest.PRESENCE_VERSION_V1,
+                "DataElementHeader is only supported in V1.");
+        Preconditions.checkArgument(dataLength >= 0, "Length should not be negative.");
+        Preconditions.checkArgument(dataLength < (1 << TAG_OFFSET),
+                "Data element should be equal or shorter than 128.");
+
+        this.mVersion = version;
+        this.mDataType = dataType;
+        this.mDataLength = dataLength;
+    }
+
+    /**
+     * The total type of the data element.
+     */
+    @DataElement.DataType
+    public int getDataType() {
+        return mDataType;
+    }
+
+    /**
+     * The total length of a Data Element field.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /** Serialize a {@link DataElementHeader} object into bytes. */
+    public byte[] toBytes() {
+        Preconditions.checkState(mVersion == BroadcastRequest.PRESENCE_VERSION_V1,
+                "DataElementHeader is only supported in V1.");
+        // Only 1 byte needed for the header
+        if (mDataType < (1 << SINGLE_AVAILABLE_TYPE_BIT)
+                && mDataLength < (1 << SINGLE_AVAILABLE_LENGTH_BIT)) {
+            return new byte[]{createSingleByteHeader(mDataType, mDataLength)};
+        }
+
+        return createMultipleBytesHeader(mDataType, mDataLength);
+    }
+
+    /** Creates a {@link DataElementHeader} object from bytes. */
+    @Nullable
+    public static DataElementHeader fromBytes(@BroadcastRequest.BroadcastVersion int version,
+            @Nonnull byte[] bytes) {
+        Objects.requireNonNull(bytes, "Data parsed in for DataElement should not be null.");
+
+        if (bytes.length == 0) {
+            return null;
+        }
+
+        if (bytes.length == 1) {
+            if (isExtending(bytes[0])) {
+                throw new IllegalArgumentException("The header is not complete.");
+            }
+            return new DataElementHeader(BroadcastRequest.PRESENCE_VERSION_V1,
+                    getTypeSingleByte(bytes[0]), getLengthSingleByte(bytes[0]));
+        }
+
+        // The first byte should be length and there should be at least 1 more byte following to
+        // represent type.
+        // The last header byte's MSB should be 0.
+        if (!isExtending(bytes[0]) || isExtending(bytes[bytes.length - 1])) {
+            throw new IllegalArgumentException("The header format is wrong.");
+        }
+
+        return new DataElementHeader(version,
+                getTypeMultipleBytes(Arrays.copyOfRange(bytes, 1, bytes.length)),
+                getHeaderValue(bytes[0]));
+    }
+
+    /** Creates a header based on type and length.
+     * This is used when the type is <= 16 and length is <= 7. */
+    static byte createSingleByteHeader(int type, int length) {
+        return (byte) (convertTag(/* extend= */ false)
+                | convertLengthSingleByte(length)
+                | convertTypeSingleByte(type));
+    }
+
+    /** Creates a header based on type and length.
+     * This is used when the type is > 16 or length is > 7. */
+    static byte[] createMultipleBytesHeader(int type, int length) {
+        List<Byte> typeIntList = convertTypeMultipleBytes(type);
+        byte[] res = new byte[typeIntList.size() + MULTIPLE_LENGTH_BYTE];
+        int index = 0;
+        res[index++] = convertLengthMultipleBytes(length);
+
+        for (int typeInt : typeIntList) {
+            res[index++] = (byte) typeInt;
+        }
+        return res;
+    }
+
+    /** Constructs a Data Element header with length indicated in byte format.
+     * The most significant bit is the tag, 2- 4 bits are the length, 5 - 8 bits are the type.
+     */
+    @VisibleForTesting
+    static int convertLengthSingleByte(int length) {
+        Preconditions.checkArgument(length >= 0, "Length should not be negative.");
+        Preconditions.checkArgument(length < (1 << SINGLE_AVAILABLE_LENGTH_BIT),
+                "In single Data Element header, length should be shorter than 8.");
+        return (length << SINGLE_LENGTH_OFFSET) & SINGLE_LENGTH_BITMASK;
+    }
+
+    /** Constructs a Data Element header with type indicated in byte format.
+     * The most significant bit is the tag, 2- 4 bits are the length, 5 - 8 bits are the type.
+     */
+    @VisibleForTesting
+    static int convertTypeSingleByte(int type) {
+        Preconditions.checkArgument(type >= 0, "Type should not be negative.");
+        Preconditions.checkArgument(type < (1 << SINGLE_AVAILABLE_TYPE_BIT),
+                "In single Data Element header, type should be smaller than 16.");
+
+        return type & SINGLE_TYPE_BITMASK;
+    }
+
+    /**
+     * Gets the length of Data Element from the header. (When there is only 1 byte of header)
+     */
+    static int getLengthSingleByte(byte header) {
+        Preconditions.checkArgument(!isExtending(header),
+                "Cannot apply this method for the extending header.");
+        return (header & SINGLE_LENGTH_BITMASK) >> SINGLE_LENGTH_OFFSET;
+    }
+
+    /**
+     * Gets the type of Data Element from the header. (When there is only 1 byte of header)
+     */
+    static int getTypeSingleByte(byte header) {
+        Preconditions.checkArgument(!isExtending(header),
+                "Cannot apply this method for the extending header.");
+        return header & SINGLE_TYPE_BITMASK;
+    }
+
+    /** Creates a DE(data element) header based on length.
+     * This is used when header is more than 1 byte. The first byte is always the length.
+     */
+    static byte convertLengthMultipleBytes(int length) {
+        Preconditions.checkArgument(length < (1 << TAG_OFFSET),
+                "Data element should be equal or shorter than 128.");
+        return (byte) (convertTag(/* extend= */ true) | (length & MULTIPLE_BITMASK));
+    }
+
+    /** Creates a DE(data element) header based on type.
+     * This is used when header is more than 1 byte. The first byte is always the length.
+     */
+    @VisibleForTesting
+    static List<Byte> convertTypeMultipleBytes(int type) {
+        List<Byte> typeBytes = new ArrayList<>();
+        while (type > 0) {
+            byte current = (byte) (type & MULTIPLE_BITMASK);
+            type = type >> TAG_OFFSET;
+            typeBytes.add(current);
+        }
+
+        Collections.reverse(typeBytes);
+        int size = typeBytes.size();
+        // The last byte's MSB should be 0.
+        for (int i = 0; i < size - 1; i++) {
+            typeBytes.set(i, (byte) (convertTag(/* extend= */ true) | typeBytes.get(i)));
+        }
+        return typeBytes;
+    }
+
+    /** Creates a DE(data element) header based on type.
+     * This is used when header is more than 1 byte. The first byte is always the length.
+     * Uses Integer when doing bit operation to avoid error.
+     */
+    @VisibleForTesting
+    static int getTypeMultipleBytes(byte[] typeByteArray) {
+        int type = 0;
+        int size = typeByteArray.length;
+        for (int i = 0; i < size; i++) {
+            type = (type << TAG_OFFSET) | getHeaderValue(typeByteArray[i]);
+        }
+        return type;
+    }
+
+    /** Gets the integer value of the 7 bits in the header. (The MSB is tag) */
+    @VisibleForTesting
+    static int getHeaderValue(byte header) {
+        return (header & MULTIPLE_BITMASK);
+    }
+
+    /** Sets the MSB of the header byte. If this is the last byte of headers, MSB is 0.
+     * If there are at least header following, the MSB is 1.
+     */
+    @VisibleForTesting
+    static byte convertTag(boolean extend) {
+        return (byte) (extend ? 0b10000000 : 0b00000000);
+    }
+
+    /** Returns {@code true} if there are at least 1 byte of header after the current one. */
+    @VisibleForTesting
+    static boolean isExtending(byte header) {
+        return (header & TAG_BITMASK) != 0;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
new file mode 100644
index 0000000..34a7514
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.Nullable;
+import android.nearby.BroadcastRequest;
+import android.nearby.DataElement;
+import android.nearby.PresenceBroadcastRequest;
+import android.nearby.PresenceCredential;
+import android.nearby.PublicCredential;
+import android.util.Log;
+
+import com.android.server.nearby.util.encryption.Cryptor;
+import com.android.server.nearby.util.encryption.CryptorImpFake;
+import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
+import com.android.server.nearby.util.encryption.CryptorImpV1;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A Nearby Presence advertisement to be advertised on BT5.0 devices.
+ *
+ * <p>Serializable between Java object and bytes formats. Java object is used at the upper scanning
+ * and advertising interface as an abstraction of the actual bytes. Bytes format is used at the
+ * underlying BLE and mDNS stacks, which do necessary slicing and merging based on advertising
+ * capacities.
+ *
+ * The extended advertisement is defined in the format below:
+ * Header (1 byte) | salt (1+2 bytes) | Identity + filter (2+16 bytes)
+ * | repeated DE fields (various bytes)
+ * The header contains:
+ * version (3 bits) | 5 bit reserved for future use (RFU)
+ */
+public class ExtendedAdvertisement extends Advertisement{
+
+    public static final int SALT_DATA_LENGTH = 2;
+
+    static final int HEADER_LENGTH = 1;
+
+    static final int IDENTITY_DATA_LENGTH = 16;
+
+    private final List<DataElement> mDataElements;
+
+    private final byte[] mAuthenticityKey;
+
+    // All Data Elements including salt and identity.
+    // Each list item (byte array) is a Data Element (with its header).
+    private final List<byte[]> mCompleteDataElementsBytes;
+    // Signature generated from data elements.
+    private final byte[] mHmacTag;
+
+    /**
+     * Creates an {@link ExtendedAdvertisement} from a Presence Broadcast Request.
+     * @return {@link ExtendedAdvertisement} object. {@code null} when the request is illegal.
+     */
+    @Nullable
+    public static ExtendedAdvertisement createFromRequest(PresenceBroadcastRequest request) {
+        if (request.getVersion() != BroadcastRequest.PRESENCE_VERSION_V1) {
+            Log.v(TAG, "ExtendedAdvertisement only supports V1 now.");
+            return null;
+        }
+
+        byte[] salt = request.getSalt();
+        if (salt.length != SALT_DATA_LENGTH) {
+            Log.v(TAG, "Salt does not match correct length");
+            return null;
+        }
+
+        byte[] identity = request.getCredential().getMetadataEncryptionKey();
+        byte[] authenticityKey = request.getCredential().getAuthenticityKey();
+        if (identity.length != IDENTITY_DATA_LENGTH) {
+            Log.v(TAG, "Identity does not match correct length");
+            return null;
+        }
+
+        List<Integer> actions = request.getActions();
+        if (actions.isEmpty()) {
+            Log.v(TAG, "ExtendedAdvertisement must contain at least one action");
+            return null;
+        }
+
+        List<DataElement> dataElements = request.getExtendedProperties();
+        return new ExtendedAdvertisement(
+                request.getCredential().getIdentityType(),
+                identity,
+                salt,
+                authenticityKey,
+                actions,
+                dataElements);
+    }
+
+    /** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */
+    @Nullable
+    public byte[] toBytes() {
+        ByteBuffer buffer = ByteBuffer.allocate(getLength());
+
+        // Header
+        buffer.put(ExtendedAdvertisementUtils.constructHeader(getVersion()));
+
+        // Salt
+        buffer.put(mCompleteDataElementsBytes.get(0));
+
+        // Identity
+        buffer.put(mCompleteDataElementsBytes.get(1));
+
+        List<Byte> rawDataBytes = new ArrayList<>();
+        // Data Elements (Already includes salt and identity)
+        for (int i = 2; i < mCompleteDataElementsBytes.size(); i++) {
+            byte[] dataElementBytes = mCompleteDataElementsBytes.get(i);
+            for (Byte b : dataElementBytes) {
+                rawDataBytes.add(b);
+            }
+        }
+
+        byte[] dataElements = new byte[rawDataBytes.size()];
+        for (int i = 0; i < rawDataBytes.size(); i++) {
+            dataElements[i] = rawDataBytes.get(i);
+        }
+
+        buffer.put(
+                getCryptor(/* encrypt= */ true).encrypt(dataElements, getSalt(), mAuthenticityKey));
+
+        buffer.put(mHmacTag);
+
+        return buffer.array();
+    }
+
+    /** Deserialize from bytes into an {@link ExtendedAdvertisement} object.
+     * {@code null} when there is something when parsing.
+     */
+    @Nullable
+    public static ExtendedAdvertisement fromBytes(byte[] bytes, PublicCredential publicCredential) {
+        @BroadcastRequest.BroadcastVersion
+        int version = ExtendedAdvertisementUtils.getVersion(bytes);
+        if (version != PresenceBroadcastRequest.PRESENCE_VERSION_V1) {
+            Log.v(TAG, "ExtendedAdvertisement is used in V1 only and version is " + version);
+            return null;
+        }
+
+        byte[] authenticityKey = publicCredential.getAuthenticityKey();
+
+        int index = HEADER_LENGTH;
+        // Salt
+        byte[] saltHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index);
+        DataElementHeader saltHeader = DataElementHeader.fromBytes(version, saltHeaderArray);
+        if (saltHeader == null || saltHeader.getDataType() != DataElement.DataType.SALT) {
+            Log.v(TAG, "First data element has to be salt.");
+            return null;
+        }
+        index += saltHeaderArray.length;
+        byte[] salt = new byte[saltHeader.getDataLength()];
+        for (int i = 0; i < saltHeader.getDataLength(); i++) {
+            salt[i] = bytes[index++];
+        }
+
+        // Identity
+        byte[] identityHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index);
+        DataElementHeader identityHeader =
+                DataElementHeader.fromBytes(version, identityHeaderArray);
+        if (identityHeader == null) {
+            Log.v(TAG, "The second element has to be identity.");
+            return null;
+        }
+        index += identityHeaderArray.length;
+        @PresenceCredential.IdentityType int identityType =
+                toPresenceCredentialIdentityType(identityHeader.getDataType());
+        if (identityType == PresenceCredential.IDENTITY_TYPE_UNKNOWN) {
+            Log.v(TAG, "The identity type is unknown.");
+            return null;
+        }
+        byte[] encryptedIdentity = new byte[identityHeader.getDataLength()];
+        for (int i = 0; i < identityHeader.getDataLength(); i++) {
+            encryptedIdentity[i] = bytes[index++];
+        }
+        byte[] identity =
+                CryptorImpIdentityV1
+                        .getInstance().decrypt(encryptedIdentity, salt, authenticityKey);
+
+        Cryptor cryptor = getCryptor(/* encrypt= */ true);
+        byte[] encryptedDataElements =
+                new byte[bytes.length - index - cryptor.getSignatureLength()];
+        // Decrypt other data elements
+        System.arraycopy(bytes, index, encryptedDataElements, 0, encryptedDataElements.length);
+        byte[] decryptedDataElements =
+                cryptor.decrypt(encryptedDataElements, salt, authenticityKey);
+        if (decryptedDataElements == null) {
+            return null;
+        }
+
+        // Verify the computed HMAC tag is equal to HMAC tag in advertisement
+        if (cryptor.getSignatureLength() > 0) {
+            byte[] expectedHmacTag = new byte[cryptor.getSignatureLength()];
+            System.arraycopy(
+                    bytes, bytes.length - cryptor.getSignatureLength(),
+                    expectedHmacTag, 0, cryptor.getSignatureLength());
+            if (!cryptor.verify(decryptedDataElements, authenticityKey, expectedHmacTag)) {
+                Log.e(TAG, "HMAC tags not match.");
+                return null;
+            }
+        }
+
+        int dataElementArrayIndex = 0;
+        // Other Data Elements
+        List<Integer> actions = new ArrayList<>();
+        List<DataElement> dataElements = new ArrayList<>();
+        while (dataElementArrayIndex < decryptedDataElements.length) {
+            byte[] deHeaderArray = ExtendedAdvertisementUtils
+                    .getDataElementHeader(decryptedDataElements, dataElementArrayIndex);
+            DataElementHeader deHeader = DataElementHeader.fromBytes(version, deHeaderArray);
+            dataElementArrayIndex += deHeaderArray.length;
+
+            @DataElement.DataType int type = Objects.requireNonNull(deHeader).getDataType();
+            if (type == DataElement.DataType.ACTION) {
+                if (deHeader.getDataLength() != 1) {
+                    Log.v(TAG, "Action id should only 1 byte.");
+                    return null;
+                }
+                actions.add((int) decryptedDataElements[dataElementArrayIndex++]);
+            } else {
+                if (isSaltOrIdentity(type)) {
+                    Log.v(TAG, "Type " + type + " is duplicated. There should be only one salt"
+                            + " and one identity in the advertisement.");
+                    return null;
+                }
+                byte[] deData = new byte[deHeader.getDataLength()];
+                for (int i = 0; i < deHeader.getDataLength(); i++) {
+                    deData[i] = decryptedDataElements[dataElementArrayIndex++];
+                }
+                dataElements.add(new DataElement(type, deData));
+            }
+        }
+
+        return new ExtendedAdvertisement(identityType, identity, salt, authenticityKey, actions,
+                dataElements);
+    }
+
+    /** Returns the {@link DataElement}s in the advertisement. */
+    public List<DataElement> getDataElements() {
+        return new ArrayList<>(mDataElements);
+    }
+
+    /** Returns the {@link DataElement}s in the advertisement according to the key. */
+    public List<DataElement> getDataElements(@DataElement.DataType int key) {
+        List<DataElement> res = new ArrayList<>();
+        for (DataElement dataElement : mDataElements) {
+            if (key == dataElement.getKey()) {
+                res.add(dataElement);
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "ExtendedAdvertisement:"
+                        + "<VERSION: %s, length: %s, dataElementCount: %s, identityType: %s,"
+                        + " identity: %s, salt: %s, actions: %s>",
+                getVersion(),
+                getLength(),
+                getDataElements().size(),
+                getIdentityType(),
+                Arrays.toString(getIdentity()),
+                Arrays.toString(getSalt()),
+                getActions());
+    }
+
+    ExtendedAdvertisement(
+            @PresenceCredential.IdentityType int identityType,
+            byte[] identity,
+            byte[] salt,
+            byte[] authenticityKey,
+            List<Integer> actions,
+            List<DataElement> dataElements) {
+        this.mVersion = BroadcastRequest.PRESENCE_VERSION_V1;
+        this.mIdentityType = identityType;
+        this.mIdentity = identity;
+        this.mSalt = salt;
+        this.mAuthenticityKey = authenticityKey;
+        this.mActions = actions;
+        this.mDataElements = dataElements;
+        this.mCompleteDataElementsBytes = new ArrayList<>();
+
+        int length = HEADER_LENGTH; // header
+
+        // Salt
+        DataElement saltElement = new DataElement(DataElement.DataType.SALT, salt);
+        byte[] saltByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(saltElement);
+        mCompleteDataElementsBytes.add(saltByteArray);
+        length += saltByteArray.length;
+
+        // Identity
+        byte[] encryptedIdentity =
+                CryptorImpIdentityV1.getInstance().encrypt(identity, salt, authenticityKey);
+        DataElement identityElement = new DataElement(toDataType(identityType), encryptedIdentity);
+        byte[] identityByteArray =
+                ExtendedAdvertisementUtils.convertDataElementToBytes(identityElement);
+        mCompleteDataElementsBytes.add(identityByteArray);
+        length += identityByteArray.length;
+
+        List<Byte> dataElementBytes = new ArrayList<>();
+        // Intents
+        for (int action : mActions) {
+            DataElement actionElement = new DataElement(DataElement.DataType.ACTION,
+                    new byte[] {(byte) action});
+            byte[] intentByteArray =
+                    ExtendedAdvertisementUtils.convertDataElementToBytes(actionElement);
+            mCompleteDataElementsBytes.add(intentByteArray);
+            for (Byte b : intentByteArray) {
+                dataElementBytes.add(b);
+            }
+        }
+
+        // Data Elements (Extended properties)
+        for (DataElement dataElement : mDataElements) {
+            byte[] deByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(dataElement);
+            mCompleteDataElementsBytes.add(deByteArray);
+            for (Byte b : deByteArray) {
+                dataElementBytes.add(b);
+            }
+        }
+
+        byte[] data = new byte[dataElementBytes.size()];
+        for (int i = 0; i < dataElementBytes.size(); i++) {
+            data[i] = dataElementBytes.get(i);
+        }
+        Cryptor cryptor = getCryptor(/* encrypt= */ true);
+        byte[] encryptedDeBytes = cryptor.encrypt(data, salt, authenticityKey);
+
+        length += encryptedDeBytes.length;
+
+        // Signature
+        byte[] hmacTag = Objects.requireNonNull(cryptor.sign(data, authenticityKey));
+        mHmacTag = hmacTag;
+        length += hmacTag.length;
+
+        this.mLength = length;
+    }
+
+    @PresenceCredential.IdentityType
+    private static int toPresenceCredentialIdentityType(@DataElement.DataType int type) {
+        switch (type) {
+            case DataElement.DataType.PRIVATE_IDENTITY:
+                return PresenceCredential.IDENTITY_TYPE_PRIVATE;
+            case DataElement.DataType.PROVISIONED_IDENTITY:
+                return PresenceCredential.IDENTITY_TYPE_PROVISIONED;
+            case DataElement.DataType.TRUSTED_IDENTITY:
+                return PresenceCredential.IDENTITY_TYPE_TRUSTED;
+            case DataElement.DataType.PUBLIC_IDENTITY:
+            default:
+                return PresenceCredential.IDENTITY_TYPE_UNKNOWN;
+        }
+    }
+
+    @DataElement.DataType
+    private static int toDataType(@PresenceCredential.IdentityType int identityType) {
+        switch (identityType) {
+            case PresenceCredential.IDENTITY_TYPE_PRIVATE:
+                return DataElement.DataType.PRIVATE_IDENTITY;
+            case PresenceCredential.IDENTITY_TYPE_PROVISIONED:
+                return DataElement.DataType.PROVISIONED_IDENTITY;
+            case PresenceCredential.IDENTITY_TYPE_TRUSTED:
+                return DataElement.DataType.TRUSTED_IDENTITY;
+            case PresenceCredential.IDENTITY_TYPE_UNKNOWN:
+            default:
+                return DataElement.DataType.PUBLIC_IDENTITY;
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given {@link DataElement.DataType} is salt, or one of the
+     * identities. Identities should be able to convert to {@link PresenceCredential.IdentityType}s.
+     */
+    private static boolean isSaltOrIdentity(@DataElement.DataType int type) {
+        return type == DataElement.DataType.SALT || type == DataElement.DataType.PRIVATE_IDENTITY
+                || type == DataElement.DataType.TRUSTED_IDENTITY
+                || type == DataElement.DataType.PROVISIONED_IDENTITY
+                || type == DataElement.DataType.PUBLIC_IDENTITY;
+    }
+
+    private static Cryptor getCryptor(boolean encrypt) {
+        if (encrypt) {
+            Log.d(TAG, "get V1 Cryptor");
+            return CryptorImpV1.getInstance();
+        }
+        Log.d(TAG, "get fake Cryptor");
+        return CryptorImpFake.getInstance();
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisementUtils.java b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisementUtils.java
new file mode 100644
index 0000000..06d0f2b
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisementUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.android.server.nearby.presence.ExtendedAdvertisement.HEADER_LENGTH;
+
+import android.annotation.SuppressLint;
+import android.nearby.BroadcastRequest;
+import android.nearby.DataElement;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides serialization and deserialization util methods for {@link ExtendedAdvertisement}.
+ */
+public final class ExtendedAdvertisementUtils {
+
+    // Advertisement header related static fields.
+    private static final int VERSION_MASK = 0b11100000;
+    private static final int VERSION_MASK_AFTER_SHIT = 0b00000111;
+    private static final int HEADER_INDEX = 0;
+    private static final int HEADER_VERSION_OFFSET = 5;
+
+    /**
+     * Constructs the header of a {@link ExtendedAdvertisement}.
+     * 3 bit version, and 5 bit reserved for future use (RFU).
+     */
+    public static byte constructHeader(@BroadcastRequest.BroadcastVersion int version) {
+        return (byte) ((version << 5) & VERSION_MASK);
+    }
+
+    /** Returns the {@link BroadcastRequest.BroadcastVersion} from the advertisement
+     * in bytes format. */
+    public static int getVersion(byte[] advertisement) {
+        if (advertisement.length < HEADER_LENGTH) {
+            throw new IllegalArgumentException("Advertisement must contain header");
+        }
+        return ((advertisement[HEADER_INDEX] & VERSION_MASK) >> HEADER_VERSION_OFFSET)
+                & VERSION_MASK_AFTER_SHIT;
+    }
+
+    /** Returns the {@link DataElementHeader} from the advertisement in bytes format. */
+    public static byte[] getDataElementHeader(byte[] advertisement, int startIndex) {
+        Preconditions.checkArgument(startIndex < advertisement.length,
+                "Advertisement has no longer data left.");
+        List<Byte> headerBytes = new ArrayList<>();
+        while (startIndex < advertisement.length) {
+            byte current = advertisement[startIndex];
+            headerBytes.add(current);
+            if (!DataElementHeader.isExtending(current)) {
+                int size = headerBytes.size();
+                byte[] res = new byte[size];
+                for (int i = 0; i < size; i++) {
+                    res[i] = headerBytes.get(i);
+                }
+                return res;
+            }
+            startIndex++;
+        }
+        throw new IllegalArgumentException("There is no end of the DataElement header.");
+    }
+
+    /**
+     * Constructs {@link DataElement}, including header(s) and actual data element data.
+     *
+     * Suppresses warning because {@link DataElement} checks isValidType in constructor.
+     */
+    @SuppressLint("WrongConstant")
+    public static byte[] convertDataElementToBytes(DataElement dataElement) {
+        @DataElement.DataType int type = dataElement.getKey();
+        byte[] data = dataElement.getValue();
+        DataElementHeader header = new DataElementHeader(BroadcastRequest.PRESENCE_VERSION_V1,
+                type, data.length);
+        byte[] headerByteArray = header.toBytes();
+
+        byte[] res = new byte[headerByteArray.length + data.length];
+        System.arraycopy(headerByteArray, 0, res, 0, headerByteArray.length);
+        System.arraycopy(data, 0, res, headerByteArray.length, data.length);
+        return res;
+    }
+
+    private ExtendedAdvertisementUtils() {}
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/FastAdvertisement.java b/nearby/service/java/com/android/server/nearby/presence/FastAdvertisement.java
index e4df673..ae53ada 100644
--- a/nearby/service/java/com/android/server/nearby/presence/FastAdvertisement.java
+++ b/nearby/service/java/com/android/server/nearby/presence/FastAdvertisement.java
@@ -24,7 +24,6 @@
 import com.android.internal.util.Preconditions;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -42,7 +41,7 @@
 // The header contains:
 // version (3 bits) | provision_mode_flag (1 bit) | identity_type (3 bits) |
 // extended_advertisement_mode (1 bit)
-public class FastAdvertisement {
+public class FastAdvertisement extends Advertisement {
 
     private static final int FAST_ADVERTISEMENT_MAX_LENGTH = 24;
 
@@ -85,7 +84,8 @@
                 (byte) request.getTxPower());
     }
 
-    /** Serialize an {@link FastAdvertisement} object into bytes. */
+    /** Serialize a {@link FastAdvertisement} object into bytes. */
+    @Override
     public byte[] toBytes() {
         ByteBuffer buffer = ByteBuffer.allocate(getLength());
 
@@ -100,18 +100,8 @@
         return buffer.array();
     }
 
-    private final int mLength;
-
     private final int mLtvFieldCount;
 
-    @PresenceCredential.IdentityType private final int mIdentityType;
-
-    private final byte[] mIdentity;
-
-    private final byte[] mSalt;
-
-    private final List<Integer> mActions;
-
     @Nullable
     private final Byte mTxPower;
 
@@ -121,6 +111,7 @@
             byte[] salt,
             List<Integer> actions,
             @Nullable Byte txPower) {
+        this.mVersion = BroadcastRequest.PRESENCE_VERSION_V0;
         this.mIdentityType = identityType;
         this.mIdentity = identity;
         this.mSalt = salt;
@@ -143,44 +134,12 @@
                 "FastAdvertisement exceeds maximum length");
     }
 
-    /** Returns the version in the advertisement. */
-    @BroadcastRequest.BroadcastVersion
-    public int getVersion() {
-        return BroadcastRequest.PRESENCE_VERSION_V0;
-    }
-
-    /** Returns the identity type in the advertisement. */
-    @PresenceCredential.IdentityType
-    public int getIdentityType() {
-        return mIdentityType;
-    }
-
-    /** Returns the identity bytes in the advertisement. */
-    public byte[] getIdentity() {
-        return mIdentity.clone();
-    }
-
-    /** Returns the salt of the advertisement. */
-    public byte[] getSalt() {
-        return mSalt.clone();
-    }
-
-    /** Returns the actions in the advertisement. */
-    public List<Integer> getActions() {
-        return new ArrayList<>(mActions);
-    }
-
     /** Returns the adjusted TX Power in the advertisement. Null if not available. */
     @Nullable
     public Byte getTxPower() {
         return mTxPower;
     }
 
-    /** Returns the length of the advertisement. */
-    public int getLength() {
-        return mLength;
-    }
-
     /** Returns the count of LTV fields in the advertisement. */
     public int getLtvFieldCount() {
         return mLtvFieldCount;
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
index d1c72ae..5a76d96 100644
--- a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
@@ -16,31 +16,55 @@
 
 package com.android.server.nearby.presence;
 
-import android.nearby.NearbyDevice;
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.NonNull;
+import android.nearby.DataElement;
 import android.nearby.NearbyDeviceParcelable;
 import android.nearby.PresenceDevice;
 import android.nearby.PresenceScanFilter;
 import android.nearby.PublicCredential;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 /** Represents a Presence discovery result. */
 public class PresenceDiscoveryResult {
 
     /** Creates a {@link PresenceDiscoveryResult} from the scan data. */
     public static PresenceDiscoveryResult fromDevice(NearbyDeviceParcelable device) {
+        PresenceDevice presenceDevice = device.getPresenceDevice();
+        if (presenceDevice != null) {
+            return new PresenceDiscoveryResult.Builder()
+                    .setTxPower(device.getTxPower())
+                    .setRssi(device.getRssi())
+                    .setSalt(presenceDevice.getSalt())
+                    .setPublicCredential(device.getPublicCredential())
+                    .addExtendedProperties(presenceDevice.getExtendedProperties())
+                    .setEncryptedIdentityTag(device.getEncryptionKeyTag())
+                    .build();
+        }
         byte[] salt = device.getSalt();
         if (salt == null) {
             salt = new byte[0];
         }
-        return new PresenceDiscoveryResult.Builder()
-                .setTxPower(device.getTxPower())
+
+        PresenceDiscoveryResult.Builder builder = new PresenceDiscoveryResult.Builder();
+        builder.setTxPower(device.getTxPower())
                 .setRssi(device.getRssi())
                 .setSalt(salt)
                 .addPresenceAction(device.getAction())
-                .setPublicCredential(device.getPublicCredential())
-                .build();
+                .setPublicCredential(device.getPublicCredential());
+        if (device.getPresenceDevice() != null) {
+            builder.addExtendedProperties(device.getPresenceDevice().getExtendedProperties());
+        }
+        return builder.build();
     }
 
     private final int mTxPower;
@@ -48,25 +72,35 @@
     private final byte[] mSalt;
     private final List<Integer> mPresenceActions;
     private final PublicCredential mPublicCredential;
+    private final List<DataElement> mExtendedProperties;
+    private final byte[] mEncryptedIdentityTag;
 
     private PresenceDiscoveryResult(
             int txPower,
             int rssi,
             byte[] salt,
             List<Integer> presenceActions,
-            PublicCredential publicCredential) {
+            PublicCredential publicCredential,
+            List<DataElement> extendedProperties,
+            byte[] encryptedIdentityTag) {
         mTxPower = txPower;
         mRssi = rssi;
         mSalt = salt;
         mPresenceActions = presenceActions;
         mPublicCredential = publicCredential;
+        mExtendedProperties = extendedProperties;
+        mEncryptedIdentityTag = encryptedIdentityTag;
     }
 
     /** Returns whether the discovery result matches the scan filter. */
     public boolean matches(PresenceScanFilter scanFilter) {
+        if (accountKeyMatches(scanFilter.getExtendedProperties())) {
+            return true;
+        }
+
         return pathLossMatches(scanFilter.getMaxPathLoss())
                 && actionMatches(scanFilter.getPresenceActions())
-                && credentialMatches(scanFilter.getCredentials());
+                && identityMatches(scanFilter.getCredentials());
     }
 
     private boolean pathLossMatches(int maxPathLoss) {
@@ -80,21 +114,47 @@
         return filterActions.stream().anyMatch(mPresenceActions::contains);
     }
 
-    private boolean credentialMatches(List<PublicCredential> credentials) {
-        return credentials.contains(mPublicCredential);
+    @VisibleForTesting
+    boolean accountKeyMatches(List<DataElement> extendedProperties) {
+        Set<byte[]> accountKeys = new ArraySet<>();
+        for (DataElement requestedDe : mExtendedProperties) {
+            if (requestedDe.getKey() != DataElement.DataType.ACCOUNT_KEY_DATA) {
+                continue;
+            }
+            accountKeys.add(requestedDe.getValue());
+        }
+        for (DataElement scannedDe : extendedProperties) {
+            if (scannedDe.getKey() != DataElement.DataType.ACCOUNT_KEY_DATA) {
+                continue;
+            }
+            // If one account key matches, then returns true.
+            for (byte[] key : accountKeys) {
+                if (Arrays.equals(key, scannedDe.getValue())) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
     }
 
-    /** Converts a presence device from the discovery result. */
-    public PresenceDevice toPresenceDevice() {
-        return new PresenceDevice.Builder(
-                // Use the public credential hash as the device Id.
-                String.valueOf(mPublicCredential.hashCode()),
-                mSalt,
-                mPublicCredential.getSecretId(),
-                mPublicCredential.getEncryptedMetadata())
-                .setRssi(mRssi)
-                .addMedium(NearbyDevice.Medium.BLE)
-                .build();
+    @VisibleForTesting
+    /** Gets presence {@link DataElement}s of the discovery result. */
+    public List<DataElement> getExtendedProperties() {
+        return mExtendedProperties;
+    }
+
+    private boolean identityMatches(List<PublicCredential> publicCredentials) {
+        if (mEncryptedIdentityTag.length == 0) {
+            return true;
+        }
+        for (PublicCredential publicCredential : publicCredentials) {
+            if (Arrays.equals(
+                    mEncryptedIdentityTag, publicCredential.getEncryptedMetadataKeyTag())) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /** Builder for {@link PresenceDiscoveryResult}. */
@@ -105,9 +165,12 @@
 
         private PublicCredential mPublicCredential;
         private final List<Integer> mPresenceActions;
+        private final List<DataElement> mExtendedProperties;
+        private byte[] mEncryptedIdentityTag = new byte[0];
 
         public Builder() {
             mPresenceActions = new ArrayList<>();
+            mExtendedProperties = new ArrayList<>();
         }
 
         /** Sets the calibrated tx power for the discovery result. */
@@ -130,7 +193,18 @@
 
         /** Sets the public credential for the discovery result. */
         public Builder setPublicCredential(PublicCredential publicCredential) {
-            mPublicCredential = publicCredential;
+            if (publicCredential != null) {
+                mPublicCredential = publicCredential;
+            }
+            return this;
+        }
+
+        /** Sets the encrypted identity tag for the discovery result. Usually it is passed from
+         * {@link NearbyDeviceParcelable} and the tag is calculated with authenticity key when
+         * receiving an advertisement.
+         */
+        public Builder setEncryptedIdentityTag(byte[] encryptedIdentityTag) {
+            mEncryptedIdentityTag = encryptedIdentityTag;
             return this;
         }
 
@@ -140,10 +214,34 @@
             return this;
         }
 
+        /** Adds presence {@link DataElement}s of the discovery result. */
+        public Builder addExtendedProperties(DataElement dataElement) {
+            if (dataElement.getKey() == DataElement.DataType.ACTION) {
+                byte[] value = dataElement.getValue();
+                if (value.length == 1) {
+                    addPresenceAction(Byte.toUnsignedInt(value[0]));
+                } else {
+                    Log.e(TAG, "invalid action data element");
+                }
+            } else {
+                mExtendedProperties.add(dataElement);
+            }
+            return this;
+        }
+
+        /** Adds presence {@link DataElement}s of the discovery result. */
+        public Builder addExtendedProperties(@NonNull List<DataElement> dataElements) {
+            for (DataElement dataElement : dataElements) {
+                addExtendedProperties(dataElement);
+            }
+            return this;
+        }
+
         /** Builds a {@link PresenceDiscoveryResult}. */
         public PresenceDiscoveryResult build() {
             return new PresenceDiscoveryResult(
-                    mTxPower, mRssi, mSalt, mPresenceActions, mPublicCredential);
+                    mTxPower, mRssi, mSalt, mPresenceActions,
+                    mPublicCredential, mExtendedProperties, mEncryptedIdentityTag);
         }
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java
new file mode 100644
index 0000000..deb5167
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nearby.DataElement;
+import android.nearby.NearbyDevice;
+import android.nearby.NearbyManager;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanCallback;
+import android.nearby.ScanRequest;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.Constant;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Executors;
+
+/** PresenceManager is the class initiated in nearby service to handle presence related work. */
+public class PresenceManager {
+
+    final LocatorContextWrapper mLocatorContextWrapper;
+    final Locator mLocator;
+    private final IntentFilter mIntentFilter;
+
+    @VisibleForTesting
+    final ScanCallback mScanCallback =
+            new ScanCallback() {
+                @Override
+                public void onDiscovered(@NonNull NearbyDevice device) {
+                    Log.i(TAG, "[PresenceManager] discovered Device.");
+                    PresenceDevice presenceDevice = (PresenceDevice) device;
+                    List<DataElement> dataElements = presenceDevice.getExtendedProperties();
+                    for (DataElement dataElement : dataElements) {
+                        Log.i(TAG, "[PresenceManager] Data Element key "
+                                + dataElement.getKey());
+                        Log.i(TAG, "[PresenceManager] Data Element value "
+                                + Arrays.toString(dataElement.getValue()));
+                    }
+                }
+
+                @Override
+                public void onUpdated(@NonNull NearbyDevice device) {}
+
+                @Override
+                public void onLost(@NonNull NearbyDevice device) {}
+
+                @Override
+                public void onError(int errorCode) {
+                    Log.w(Constant.TAG, "[PresenceManager] Scan error is " + errorCode);
+                }
+            };
+
+    private final BroadcastReceiver mScreenBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    NearbyManager manager = getNearbyManager();
+                    if (manager == null) {
+                        Log.e(TAG, "Nearby Manager is null");
+                        return;
+                    }
+                    if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+                        Log.d(TAG, "PresenceManager Start scan.");
+                        PublicCredential publicCredential =
+                                new PublicCredential.Builder(new byte[]{1}, new byte[]{1},
+                                        new byte[]{1}, new byte[]{1}, new byte[]{1}).build();
+                        PresenceScanFilter presenceScanFilter =
+                                new PresenceScanFilter.Builder()
+                                        .setMaxPathLoss(3)
+                                        .addCredential(publicCredential)
+                                        .addPresenceAction(1)
+                                        .addExtendedProperty(new DataElement(
+                                                DataElement.DataType.ACCOUNT_KEY_DATA,
+                                                new byte[16]))
+                                        .build();
+                        ScanRequest scanRequest =
+                                new ScanRequest.Builder()
+                                        .setScanType(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE)
+                                        .addScanFilter(presenceScanFilter)
+                                        .build();
+                        Log.d(
+                                TAG,
+                                String.format(
+                                        Locale.getDefault(),
+                                        "[PresenceManager] Start Presence scan with request: %s",
+                                        scanRequest.toString()));
+                        manager.startScan(
+                                scanRequest, Executors.newSingleThreadExecutor(), mScanCallback);
+                    } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                        Log.d(TAG, "PresenceManager Stop scan.");
+                        manager.stopScan(mScanCallback);
+                    }
+                }
+            };
+
+    public PresenceManager(LocatorContextWrapper contextWrapper) {
+        mLocatorContextWrapper = contextWrapper;
+        mLocator = mLocatorContextWrapper.getLocator();
+        mIntentFilter = new IntentFilter();
+    }
+
+    /** Null when the Nearby Service is not available. */
+    @Nullable
+    private NearbyManager getNearbyManager() {
+        return (NearbyManager)
+                mLocatorContextWrapper
+                        .getApplicationContext()
+                        .getSystemService(Context.NEARBY_SERVICE);
+    }
+
+    /** Function called when nearby service start. */
+    public void initiate() {
+        mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mLocatorContextWrapper
+                .getContext()
+                .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
index f136695..21b4d7c 100644
--- a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
@@ -40,7 +40,6 @@
     protected final DiscoveryProviderController mController;
     protected final Executor mExecutor;
     protected Listener mListener;
-    protected List<ScanFilter> mScanFilters;
 
     /** Interface for listening to discovery providers. */
     public interface Listener {
@@ -77,6 +76,12 @@
     protected void invalidateScanMode() {}
 
     /**
+     * Callback invoked to inform the provider of new provider scan filters which replaces any prior
+     * provider filters. Always invoked on the provider executor.
+     */
+    protected void onSetScanFilters(List<ScanFilter> filters) {}
+
+    /**
      * Retrieves the controller for this discovery provider. Should never be invoked by subclasses,
      * as a discovery provider should not be controlling itself. Using this method from subclasses
      * could also result in deadlock.
@@ -138,7 +143,7 @@
 
         @Override
         public void setProviderScanFilters(List<ScanFilter> filters) {
-            mScanFilters = filters;
+            mExecutor.execute(() -> onSetScanFilters(filters));
         }
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
index 67392ad..2b9fdb9 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
@@ -16,17 +16,25 @@
 
 package com.android.server.nearby.provider;
 
+import static com.android.server.nearby.NearbyService.TAG;
+import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.le.AdvertiseCallback;
 import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.nearby.BroadcastCallback;
+import android.nearby.BroadcastRequest;
 import android.os.ParcelUuid;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.injector.Injector;
 
-import java.util.UUID;
 import java.util.concurrent.Executor;
 
 /**
@@ -46,13 +54,16 @@
 
     private BroadcastListener mBroadcastListener;
     private boolean mIsAdvertising;
-
+    @VisibleForTesting
+    AdvertisingSetCallback mAdvertisingSetCallback;
     BleBroadcastProvider(Injector injector, Executor executor) {
         mInjector = injector;
         mExecutor = executor;
+        mAdvertisingSetCallback = getAdvertisingSetCallback();
     }
 
-    void start(byte[] advertisementPackets, BroadcastListener listener) {
+    void start(@BroadcastRequest.BroadcastVersion int version, byte[] advertisementPackets,
+            BroadcastListener listener) {
         if (mIsAdvertising) {
             stop();
         }
@@ -63,23 +74,36 @@
                     mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser();
             if (bluetoothLeAdvertiser != null) {
                 advertiseStarted = true;
-                AdvertiseSettings settings =
-                        new AdvertiseSettings.Builder()
-                                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
-                                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
-                                .setConnectable(true)
-                                .build();
-
-                // TODO(b/230538655) Use empty data until Presence V1 protocol is implemented.
-                ParcelUuid emptyParcelUuid = new ParcelUuid(new UUID(0L, 0L));
-                byte[] emptyAdvertisementPackets = new byte[0];
                 AdvertiseData advertiseData =
                         new AdvertiseData.Builder()
-                                .addServiceData(emptyParcelUuid, emptyAdvertisementPackets).build();
+                                .addServiceData(new ParcelUuid(PRESENCE_UUID),
+                                        advertisementPackets).build();
                 try {
                     mBroadcastListener = listener;
-                    bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, this);
+                    switch (version) {
+                        case BroadcastRequest.PRESENCE_VERSION_V0:
+                            bluetoothLeAdvertiser.startAdvertising(getAdvertiseSettings(),
+                                    advertiseData, this);
+                            break;
+                        case BroadcastRequest.PRESENCE_VERSION_V1:
+                            if (adapter.isLeExtendedAdvertisingSupported()) {
+                                bluetoothLeAdvertiser.startAdvertisingSet(
+                                        getAdvertisingSetParameters(),
+                                        advertiseData,
+                                        null, null, null, mAdvertisingSetCallback);
+                            } else {
+                                Log.w(TAG, "Failed to start advertising set because the chipset"
+                                        + " does not supports LE Extended Advertising feature.");
+                                advertiseStarted = false;
+                            }
+                            break;
+                        default:
+                            Log.w(TAG, "Failed to start advertising set because the advertisement"
+                                    + " is wrong.");
+                            advertiseStarted = false;
+                    }
                 } catch (NullPointerException | IllegalStateException | SecurityException e) {
+                    Log.w(TAG, "Failed to start advertising.", e);
                     advertiseStarted = false;
                 }
             }
@@ -97,6 +121,7 @@
                         mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser();
                 if (bluetoothLeAdvertiser != null) {
                     bluetoothLeAdvertiser.stopAdvertising(this);
+                    bluetoothLeAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback);
                 }
             }
             mBroadcastListener = null;
@@ -120,4 +145,41 @@
             mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
         }
     }
+
+    private static AdvertiseSettings getAdvertiseSettings() {
+        return new AdvertiseSettings.Builder()
+                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+                .setConnectable(true)
+                .build();
+    }
+
+    private static AdvertisingSetParameters getAdvertisingSetParameters() {
+        return new AdvertisingSetParameters.Builder()
+                .setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM)
+                .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
+                .setIncludeTxPower(true)
+                .setConnectable(true)
+                .build();
+    }
+
+    private AdvertisingSetCallback getAdvertisingSetCallback() {
+        return new AdvertisingSetCallback() {
+            @Override
+            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet,
+                    int txPower, int status) {
+                if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                    if (mBroadcastListener != null) {
+                        mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_OK);
+                    }
+                    mIsAdvertising = true;
+                } else {
+                    Log.e(TAG, "Starts advertising failed in status " + status);
+                    if (mBroadcastListener != null) {
+                        mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
+                    }
+                }
+            }
+        };
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index e8aea79..55176ba 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -27,16 +27,23 @@
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.content.Context;
+import android.nearby.DataElement;
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
 import android.nearby.ScanRequest;
 import android.os.ParcelUuid;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.nearby.common.bluetooth.fastpair.Constants;
 import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.ExtendedAdvertisement;
 import com.android.server.nearby.presence.PresenceConstants;
 import com.android.server.nearby.util.ForegroundThread;
+import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -57,6 +64,21 @@
     // Don't block the thread as it may be used by other services.
     private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
     private final Injector mInjector;
+    private final Object mLock = new Object();
+    // Null when the filters are never set
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @Nullable
+    private List<android.nearby.ScanFilter> mScanFilters;
+    private android.bluetooth.le.ScanCallback mScanCallbackLegacy =
+            new android.bluetooth.le.ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult scanResult) {
+                }
+                @Override
+                public void onScanFailed(int errorCode) {
+                }
+            };
     private android.bluetooth.le.ScanCallback mScanCallback =
             new android.bluetooth.le.ScanCallback() {
                 @Override
@@ -81,7 +103,8 @@
                             } else {
                                 byte[] presenceData = serviceDataMap.get(PRESENCE_UUID);
                                 if (presenceData != null) {
-                                    builder.setData(serviceDataMap.get(PRESENCE_UUID));
+                                    setPresenceDevice(presenceData, builder, deviceName,
+                                            scanResult.getRssi());
                                 }
                             }
                         }
@@ -91,7 +114,7 @@
 
                 @Override
                 public void onScanFailed(int errorCode) {
-                    Log.w(TAG, "BLE Scan failed with error code " + errorCode);
+                    Log.w(TAG, "BLE 5.0 Scan failed with error code " + errorCode);
                 }
             };
 
@@ -100,12 +123,39 @@
         mInjector = injector;
     }
 
+    private static PresenceDevice getPresenceDevice(ExtendedAdvertisement advertisement,
+            String deviceName, int rssi) {
+        // TODO(238458326): After implementing encryption, use real data.
+        byte[] secretIdBytes = new byte[0];
+        PresenceDevice.Builder builder =
+                new PresenceDevice.Builder(
+                        String.valueOf(advertisement.hashCode()),
+                        advertisement.getSalt(),
+                        secretIdBytes,
+                        advertisement.getIdentity())
+                        .addMedium(NearbyDevice.Medium.BLE)
+                        .setName(deviceName)
+                        .setRssi(rssi);
+        for (int i : advertisement.getActions()) {
+            builder.addExtendedProperty(new DataElement(DataElement.DataType.ACTION,
+                    new byte[]{(byte) i}));
+        }
+        for (DataElement dataElement : advertisement.getDataElements()) {
+            builder.addExtendedProperty(dataElement);
+        }
+        return builder.build();
+    }
+
     private static List<ScanFilter> getScanFilters() {
         List<ScanFilter> scanFilterList = new ArrayList<>();
         scanFilterList.add(
                 new ScanFilter.Builder()
                         .setServiceData(FAST_PAIR_UUID, new byte[]{0}, new byte[]{0})
                         .build());
+        scanFilterList.add(
+                new ScanFilter.Builder()
+                        .setServiceData(PRESENCE_UUID, new byte[]{0}, new byte[]{0})
+                        .build());
         return scanFilterList;
     }
 
@@ -130,8 +180,9 @@
     @Override
     protected void onStart() {
         if (isBleAvailable()) {
-            Log.d(TAG, "BleDiscoveryProvider started.");
-            startScan(getScanFilters(), getScanSettings(), mScanCallback);
+            Log.d(TAG, "BleDiscoveryProvider started");
+            startScan(getScanFilters(), getScanSettings(/* legacy= */ false), mScanCallback);
+            startScan(getScanFilters(), getScanSettings(/* legacy= */ true), mScanCallbackLegacy);
             return;
         }
         Log.w(TAG, "Cannot start BleDiscoveryProvider because Ble is not available.");
@@ -148,6 +199,12 @@
         }
         Log.v(TAG, "Ble scan stopped.");
         bluetoothLeScanner.stopScan(mScanCallback);
+        bluetoothLeScanner.stopScan(mScanCallbackLegacy);
+        synchronized (mLock) {
+            if (mScanFilters != null) {
+                mScanFilters = null;
+            }
+        }
     }
 
     @Override
@@ -156,6 +213,20 @@
         onStart();
     }
 
+    @Override
+    protected void onSetScanFilters(List<android.nearby.ScanFilter> filters) {
+        synchronized (mLock) {
+            mScanFilters = filters == null ? null : List.copyOf(filters);
+        }
+    }
+
+    @VisibleForTesting
+    protected List<android.nearby.ScanFilter> getFiltersLocked() {
+        synchronized (mLock) {
+            return mScanFilters == null ? null : List.copyOf(mScanFilters);
+        }
+    }
+
     private void startScan(
             List<ScanFilter> scanFilters, ScanSettings scanSettings,
             android.bluetooth.le.ScanCallback scanCallback) {
@@ -179,7 +250,7 @@
         }
     }
 
-    private ScanSettings getScanSettings() {
+    private ScanSettings getScanSettings(boolean legacy) {
         int bleScanMode = ScanSettings.SCAN_MODE_LOW_POWER;
         switch (mController.getProviderScanMode()) {
             case ScanRequest.SCAN_MODE_LOW_LATENCY:
@@ -195,11 +266,42 @@
                 bleScanMode = ScanSettings.SCAN_MODE_OPPORTUNISTIC;
                 break;
         }
-        return new ScanSettings.Builder().setScanMode(bleScanMode).build();
+        return new ScanSettings.Builder().setScanMode(bleScanMode).setLegacy(legacy).build();
     }
 
     @VisibleForTesting
     ScanCallback getScanCallback() {
         return mScanCallback;
     }
+
+    private void setPresenceDevice(byte[] data, NearbyDeviceParcelable.Builder builder,
+            String deviceName, int rssi) {
+        synchronized (mLock) {
+            if (mScanFilters == null) {
+                return;
+            }
+            for (android.nearby.ScanFilter scanFilter : mScanFilters) {
+                if (scanFilter instanceof PresenceScanFilter) {
+                    // Iterate all possible authenticity key and identity combinations to decrypt
+                    // advertisement
+                    PresenceScanFilter presenceFilter = (PresenceScanFilter) scanFilter;
+                    for (PublicCredential credential : presenceFilter.getCredentials()) {
+                        ExtendedAdvertisement advertisement =
+                                ExtendedAdvertisement.fromBytes(data, credential);
+                        if (advertisement == null) {
+                            continue;
+                        }
+                        if (CryptorImpIdentityV1.getInstance().verify(
+                                advertisement.getIdentity(),
+                                credential.getEncryptedMetadataKeyTag())) {
+                            builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName,
+                                    rssi));
+                            builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag());
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
index 3fffda5..400f936 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
@@ -16,6 +16,9 @@
 
 package com.android.server.nearby.provider;
 
+import static com.android.server.nearby.NearbyService.SUPPORT_TEST_APP;
+
+import android.annotation.Nullable;
 import android.content.Context;
 import android.nearby.BroadcastCallback;
 import android.nearby.BroadcastRequest;
@@ -27,6 +30,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.NearbyConfiguration;
 import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.Advertisement;
+import com.android.server.nearby.presence.ExtendedAdvertisement;
 import com.android.server.nearby.presence.FastAdvertisement;
 import com.android.server.nearby.util.ForegroundThread;
 
@@ -66,10 +71,12 @@
     public void startBroadcast(BroadcastRequest broadcastRequest, IBroadcastListener listener) {
         synchronized (mLock) {
             mExecutor.execute(() -> {
-                NearbyConfiguration configuration = new NearbyConfiguration();
-                if (!configuration.isPresenceBroadcastLegacyEnabled()) {
-                    reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
-                    return;
+                if (!SUPPORT_TEST_APP) {
+                    NearbyConfiguration configuration = new NearbyConfiguration();
+                    if (!configuration.isPresenceBroadcastLegacyEnabled()) {
+                        reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+                        return;
+                    }
                 }
                 if (broadcastRequest.getType() != BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE) {
                     reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
@@ -77,25 +84,37 @@
                 }
                 PresenceBroadcastRequest presenceBroadcastRequest =
                         (PresenceBroadcastRequest) broadcastRequest;
-                if (presenceBroadcastRequest.getVersion() != BroadcastRequest.PRESENCE_VERSION_V0) {
+                Advertisement advertisement = getAdvertisement(presenceBroadcastRequest);
+                if (advertisement == null) {
+                    Log.e(TAG, "Failed to start broadcast because broadcastRequest is illegal.");
                     reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
                     return;
                 }
-                FastAdvertisement fastAdvertisement = FastAdvertisement.createFromRequest(
-                        presenceBroadcastRequest);
-                byte[] advertisementPackets = fastAdvertisement.toBytes();
                 mBroadcastListener = listener;
-                mBleBroadcastProvider.start(advertisementPackets, this);
+                mBleBroadcastProvider.start(presenceBroadcastRequest.getVersion(),
+                        advertisement.toBytes(), this);
             });
         }
     }
 
+    @Nullable
+    private Advertisement getAdvertisement(PresenceBroadcastRequest request) {
+        switch (request.getVersion()) {
+            case BroadcastRequest.PRESENCE_VERSION_V0:
+                return FastAdvertisement.createFromRequest(request);
+            case BroadcastRequest.PRESENCE_VERSION_V1:
+                return ExtendedAdvertisement.createFromRequest(request);
+            default:
+                return null;
+        }
+    }
+
     /**
      * Stops the nearby broadcast.
      */
     public void stopBroadcast(IBroadcastListener listener) {
         synchronized (mLock) {
-            if (!mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) {
+            if (!SUPPORT_TEST_APP && !mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) {
                 reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
                 return;
             }
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
index 5077ffe..b24e7d9 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
@@ -19,15 +19,18 @@
 import static com.android.server.nearby.NearbyService.TAG;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.location.ContextHubClient;
 import android.hardware.location.ContextHubClientCallback;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
 import android.util.Log;
 
-import com.android.server.nearby.injector.ContextHubManagerAdapter;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.NearbyConfiguration;
 import com.android.server.nearby.injector.Injector;
 
 import com.google.common.base.Preconditions;
@@ -63,19 +66,28 @@
     }
 
     private final Injector mInjector;
+    private final Context mContext;
     private final Executor mExecutor;
 
     private boolean mStarted = false;
+    // null when CHRE availability result has not been returned
+    @Nullable private Boolean mChreSupport = null;
     @Nullable private ContextHubCommsCallback mCallback;
     @Nullable private ContextHubClient mContextHubClient;
 
-    public ChreCommunication(Injector injector, Executor executor) {
+    public ChreCommunication(Injector injector, Context context, Executor executor) {
         mInjector = injector;
+        mContext = context;
         mExecutor = executor;
     }
 
-    public boolean available() {
-        return mContextHubClient != null;
+    /**
+     * @return {@code true} if NanoApp is available and {@code null} when CHRE availability result
+     * has not been returned
+     */
+    @Nullable
+    public Boolean available() {
+        return mChreSupport;
     }
 
     /**
@@ -86,12 +98,12 @@
      *     contexthub.
      */
     public synchronized void start(ContextHubCommsCallback callback, Set<Long> nanoAppIds) {
-        ContextHubManagerAdapter manager = mInjector.getContextHubManagerAdapter();
+        ContextHubManager manager = mInjector.getContextHubManager();
         if (manager == null) {
             Log.e(TAG, "ContexHub not available in this device");
             return;
         } else {
-            Log.i(TAG, "Start ChreCommunication");
+            Log.i(TAG, "[ChreCommunication] Start ChreCommunication");
         }
         Preconditions.checkNotNull(callback);
         Preconditions.checkArgument(!nanoAppIds.isEmpty());
@@ -134,6 +146,7 @@
         if (mContextHubClient != null) {
             mContextHubClient.close();
             mContextHubClient = null;
+            mChreSupport = null;
         }
     }
 
@@ -172,7 +185,8 @@
         mCallback.onNanoAppRestart(nanoAppId);
     }
 
-    private static String contextHubTransactionResultToString(int result) {
+    @VisibleForTesting
+    static String contextHubTransactionResultToString(int result) {
         switch (result) {
             case ContextHubTransaction.RESULT_SUCCESS:
                 return "RESULT_SUCCESS";
@@ -207,13 +221,13 @@
         private final ContextHubInfo mQueriedContextHub;
         private final List<ContextHubInfo> mContextHubs;
         private final Set<Long> mNanoAppIds;
-        private final ContextHubManagerAdapter mManager;
+        private final ContextHubManager mManager;
 
         OnQueryCompleteListener(
                 ContextHubInfo queriedContextHub,
                 List<ContextHubInfo> contextHubs,
                 Set<Long> nanoAppIds,
-                ContextHubManagerAdapter manager) {
+                ContextHubManager manager) {
             this.mQueriedContextHub = queriedContextHub;
             this.mContextHubs = contextHubs;
             this.mNanoAppIds = nanoAppIds;
@@ -233,19 +247,27 @@
 
             if (response.getResult() == ContextHubTransaction.RESULT_SUCCESS) {
                 for (NanoAppState state : response.getContents()) {
+                    long version = state.getNanoAppVersion();
+                    NearbyConfiguration configuration = new NearbyConfiguration();
+                    long minVersion = configuration.getNanoAppMinVersion();
+                    if (version < minVersion) {
+                        Log.w(TAG, String.format("Current nano app version is %s, which does not  "
+                                + "meet minimum version required %s", version, minVersion));
+                        continue;
+                    }
                     if (mNanoAppIds.contains(state.getNanoAppId())) {
                         Log.i(
                                 TAG,
                                 String.format(
                                         "Found valid contexthub: %s", mQueriedContextHub.getId()));
-                        mContextHubClient =
-                                mManager.createClient(
-                                        mQueriedContextHub, ChreCommunication.this, mExecutor);
+                        mContextHubClient = mManager.createClient(mContext, mQueriedContextHub,
+                                mExecutor, ChreCommunication.this);
+                        mChreSupport = true;
                         mCallback.started(true);
                         return;
                     }
                 }
-                Log.e(
+                Log.i(
                         TAG,
                         String.format(
                                 "Didn't find the nanoapp on contexthub: %s",
@@ -263,6 +285,7 @@
             // there isn't a valid context available on this device.
             if (mContextHubs.isEmpty()) {
                 mCallback.started(false);
+                mChreSupport = false;
             }
         }
     }
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
index f20c6d8..93acede 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
@@ -20,20 +20,30 @@
 
 import static com.android.server.nearby.NearbyService.TAG;
 
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.location.NanoAppMessage;
+import android.nearby.DataElement;
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceDevice;
 import android.nearby.PresenceScanFilter;
 import android.nearby.PublicCredential;
 import android.nearby.ScanFilter;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.NearbyConfiguration;
 
-import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.ByteString;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 import service.proto.Blefilter;
@@ -42,52 +52,106 @@
 public class ChreDiscoveryProvider extends AbstractDiscoveryProvider {
     // Nanoapp ID reserved for Nearby Presence.
     /** @hide */
-    @VisibleForTesting public static final long NANOAPP_ID = 0x476f6f676c001031L;
+    @VisibleForTesting
+    public static final long NANOAPP_ID = 0x476f6f676c001031L;
     /** @hide */
-    @VisibleForTesting public static final int NANOAPP_MESSAGE_TYPE_FILTER = 3;
+    @VisibleForTesting
+    public static final int NANOAPP_MESSAGE_TYPE_FILTER = 3;
     /** @hide */
-    @VisibleForTesting public static final int NANOAPP_MESSAGE_TYPE_FILTER_RESULT = 4;
+    @VisibleForTesting
+    public static final int NANOAPP_MESSAGE_TYPE_FILTER_RESULT = 4;
+    /** @hide */
+    @VisibleForTesting
+    public static final int NANOAPP_MESSAGE_TYPE_CONFIG = 5;
 
-    private static final int PRESENCE_UUID = 0xFCF1;
+    private static final int FP_ACCOUNT_KEY_LENGTH = 16;
 
-    private ChreCommunication mChreCommunication;
-    private ChreCallback mChreCallback;
+    private final ChreCommunication mChreCommunication;
+    private final ChreCallback mChreCallback;
+    private final Object mLock = new Object();
+
     private boolean mChreStarted = false;
     private Blefilter.BleFilters mFilters = null;
-    private int mFilterId;
+    private Context mContext;
+    private NearbyConfiguration mNearbyConfiguration;
+    private final IntentFilter mIntentFilter;
+    // Null when the filters are never set
+    @GuardedBy("mLock")
+    @Nullable
+    private List<ScanFilter> mScanFilters;
+
+    private final BroadcastReceiver mScreenBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    Boolean screenOn = intent.getAction().equals(Intent.ACTION_SCREEN_ON)
+                            || intent.getAction().equals(Intent.ACTION_USER_PRESENT);
+                    Log.d(TAG, String.format(
+                            "[ChreDiscoveryProvider] update nanoapp screen status: %B", screenOn));
+                    sendScreenUpdate(screenOn);
+                }
+            };
 
     public ChreDiscoveryProvider(
             Context context, ChreCommunication chreCommunication, Executor executor) {
         super(context, executor);
+        mContext = context;
         mChreCommunication = chreCommunication;
         mChreCallback = new ChreCallback();
-        mFilterId = 0;
+        mIntentFilter = new IntentFilter();
+    }
+
+    /** Initialize the CHRE discovery provider. */
+    public void init() {
+        mChreCommunication.start(mChreCallback, Collections.singleton(NANOAPP_ID));
+        mNearbyConfiguration = new NearbyConfiguration();
     }
 
     @Override
     protected void onStart() {
         Log.d(TAG, "Start CHRE scan");
-        mChreCommunication.start(mChreCallback, Collections.singleton(NANOAPP_ID));
-        updateFilters();
+        synchronized (mLock) {
+            updateFiltersLocked();
+        }
     }
 
     @Override
     protected void onStop() {
-        mChreStarted = false;
-        mChreCommunication.stop();
+        Log.d(TAG, "Stop CHRE scan");
+        synchronized (mLock) {
+            if (mScanFilters != null) {
+                mScanFilters = null;
+            }
+            updateFiltersLocked();
+        }
     }
 
     @Override
-    protected void invalidateScanMode() {
-        onStop();
-        onStart();
+    protected void onSetScanFilters(List<ScanFilter> filters) {
+        synchronized (mLock) {
+            mScanFilters = filters == null ? null : List.copyOf(filters);
+            updateFiltersLocked();
+        }
     }
 
-    public boolean available() {
+    /**
+     * @return {@code true} if CHRE is available and {@code null} when CHRE availability result
+     * has not been returned
+     */
+    @Nullable
+    public Boolean available() {
         return mChreCommunication.available();
     }
 
-    private synchronized void updateFilters() {
+    @VisibleForTesting
+    List<ScanFilter> getFiltersLocked() {
+        synchronized (mLock) {
+            return mScanFilters == null ? null : List.copyOf(mScanFilters);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateFiltersLocked() {
         if (mScanFilters == null) {
             Log.e(TAG, "ScanFilters not set.");
             return;
@@ -95,22 +159,52 @@
         Blefilter.BleFilters.Builder filtersBuilder = Blefilter.BleFilters.newBuilder();
         for (ScanFilter scanFilter : mScanFilters) {
             PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
-            Blefilter.BleFilter filter =
-                    Blefilter.BleFilter.newBuilder()
-                            .setId(mFilterId)
-                            .setUuid(PRESENCE_UUID)
-                            .setIntent(presenceScanFilter.getPresenceActions().get(0))
-                            .build();
-            filtersBuilder.addFilter(filter);
-            mFilterId++;
+            Blefilter.BleFilter.Builder filterBuilder = Blefilter.BleFilter.newBuilder();
+            for (PublicCredential credential : presenceScanFilter.getCredentials()) {
+                filterBuilder.addCertificate(toProtoPublicCredential(credential));
+            }
+            for (DataElement dataElement : presenceScanFilter.getExtendedProperties()) {
+                if (dataElement.getKey() == DataElement.DataType.ACCOUNT_KEY_DATA) {
+                    filterBuilder.addDataElement(toProtoDataElement(dataElement));
+                } else if (mNearbyConfiguration.isTestAppSupported()
+                        && DataElement.isTestDeType(dataElement.getKey())) {
+                    filterBuilder.addDataElement(toProtoDataElement(dataElement));
+                }
+            }
+            if (!presenceScanFilter.getPresenceActions().isEmpty()) {
+                filterBuilder.setIntent(presenceScanFilter.getPresenceActions().get(0));
+            }
+            filtersBuilder.addFilter(filterBuilder.build());
         }
-        mFilters = filtersBuilder.build();
         if (mChreStarted) {
-            sendFilters(mFilters);
+            sendFilters(filtersBuilder.build());
             mFilters = null;
         }
     }
 
+    private Blefilter.PublicateCertificate toProtoPublicCredential(PublicCredential credential) {
+        Log.d(TAG, String.format("Returns a PublicCertificate with authenticity key size %d and"
+                        + " encrypted metadata key tag size %d",
+                credential.getAuthenticityKey().length,
+                credential.getEncryptedMetadataKeyTag().length));
+        return Blefilter.PublicateCertificate.newBuilder()
+                .setAuthenticityKey(ByteString.copyFrom(credential.getAuthenticityKey()))
+                .setMetadataEncryptionKeyTag(
+                        ByteString.copyFrom(credential.getEncryptedMetadataKeyTag()))
+                .build();
+    }
+
+    private Blefilter.DataElement toProtoDataElement(DataElement dataElement) {
+        return Blefilter.DataElement.newBuilder()
+                .setKey(Arrays.stream(Blefilter.DataElement.ElementType.values())
+                        .filter(p -> p.getNumber() == dataElement.getKey())
+                        .findFirst()
+                        .get())
+                .setValue(ByteString.copyFrom(dataElement.getValue()))
+                .setValueLength(dataElement.getValue().length)
+                .build();
+    }
+
     private void sendFilters(Blefilter.BleFilters filters) {
         NanoAppMessage message =
                 NanoAppMessage.createMessageToNanoApp(
@@ -120,6 +214,16 @@
         }
     }
 
+    private void sendScreenUpdate(Boolean screenOn) {
+        Blefilter.BleConfig config = Blefilter.BleConfig.newBuilder().setScreenOn(screenOn).build();
+        NanoAppMessage message =
+                NanoAppMessage.createMessageToNanoApp(
+                        NANOAPP_ID, NANOAPP_MESSAGE_TYPE_CONFIG, config.toByteArray());
+        if (!mChreCommunication.sendMessageToNanoApp(message)) {
+            Log.e(TAG, "Failed to send config to CHRE.");
+        }
+    }
+
     private class ChreCallback implements ChreCommunication.ContextHubCommsCallback {
 
         @Override
@@ -127,6 +231,10 @@
             if (success) {
                 synchronized (ChreDiscoveryProvider.this) {
                     Log.i(TAG, "CHRE communication started");
+                    mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
+                    mIntentFilter.addAction(Intent.ACTION_USER_PRESENT);
+                    mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+                    mContext.registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
                     mChreStarted = true;
                     if (mFilters != null) {
                         sendFilters(mFilters);
@@ -163,15 +271,81 @@
                     Blefilter.BleFilterResults results =
                             Blefilter.BleFilterResults.parseFrom(message.getMessageBody());
                     for (Blefilter.BleFilterResult filterResult : results.getResultList()) {
-                        Blefilter.PublicCredential credential = filterResult.getPublicCredential();
+                        // TODO(b/234653356): There are some duplicate fields set both in
+                        //  PresenceDevice and NearbyDeviceParcelable, cleanup is needed.
+                        byte[] salt = {1};
+                        byte[] secretId = {1};
+                        byte[] authenticityKey = {1};
+                        byte[] publicKey = {1};
+                        byte[] encryptedMetaData = {1};
+                        byte[] encryptedMetaDataTag = {1};
+                        if (filterResult.hasPublicCredential()) {
+                            Blefilter.PublicCredential credential =
+                                    filterResult.getPublicCredential();
+                            secretId = credential.getSecretId().toByteArray();
+                            authenticityKey = credential.getAuthenticityKey().toByteArray();
+                            publicKey = credential.getPublicKey().toByteArray();
+                            encryptedMetaData = credential.getEncryptedMetadata().toByteArray();
+                            encryptedMetaDataTag =
+                                    credential.getEncryptedMetadataTag().toByteArray();
+                        }
+                        PresenceDevice.Builder presenceDeviceBuilder =
+                                new PresenceDevice.Builder(
+                                        String.valueOf(filterResult.hashCode()),
+                                        salt,
+                                        secretId,
+                                        encryptedMetaData)
+                                        .setRssi(filterResult.getRssi())
+                                        .addMedium(NearbyDevice.Medium.BLE);
+                        // Data Elements reported from nanoapp added to Data Elements.
+                        // i.e. Fast Pair account keys, connection status and battery
+                        for (Blefilter.DataElement element : filterResult.getDataElementList()) {
+                            addDataElementsToPresenceDevice(element, presenceDeviceBuilder);
+                        }
+                        // BlE address appended to Data Element.
+                        if (filterResult.hasBluetoothAddress()) {
+                            presenceDeviceBuilder.addExtendedProperty(
+                                    new DataElement(
+                                            DataElement.DataType.BLE_ADDRESS,
+                                            filterResult.getBluetoothAddress().toByteArray()));
+                        }
+                        // BlE TX Power appended to Data Element.
+                        if (filterResult.hasTxPower()) {
+                            presenceDeviceBuilder.addExtendedProperty(
+                                    new DataElement(
+                                            DataElement.DataType.TX_POWER,
+                                            new byte[]{(byte) filterResult.getTxPower()}));
+                        }
+                        // BLE Service data appended to Data Elements.
+                        if (filterResult.hasBleServiceData()) {
+                            // Retrieves the length of the service data from the first byte,
+                            // and then skips the first byte and returns data[1 .. dataLength)
+                            // as the DataElement value.
+                            int dataLength = Byte.toUnsignedInt(
+                                    filterResult.getBleServiceData().byteAt(0));
+                            presenceDeviceBuilder.addExtendedProperty(
+                                    new DataElement(
+                                            DataElement.DataType.BLE_SERVICE_DATA,
+                                            filterResult.getBleServiceData()
+                                                    .substring(1, 1 + dataLength).toByteArray()));
+                        }
+                        // Add action
+                        if (filterResult.hasIntent()) {
+                            presenceDeviceBuilder.addExtendedProperty(
+                                    new DataElement(
+                                            DataElement.DataType.ACTION,
+                                            new byte[]{(byte) filterResult.getIntent()}));
+                        }
+
                         PublicCredential publicCredential =
                                 new PublicCredential.Builder(
-                                                credential.getSecretId().toByteArray(),
-                                                credential.getAuthenticityKey().toByteArray(),
-                                                credential.getPublicKey().toByteArray(),
-                                                credential.getEncryptedMetadata().toByteArray(),
-                                                credential.getEncryptedMetadataTag().toByteArray())
+                                        secretId,
+                                        authenticityKey,
+                                        publicKey,
+                                        encryptedMetaData,
+                                        encryptedMetaDataTag)
                                         .build();
+
                         NearbyDeviceParcelable device =
                                 new NearbyDeviceParcelable.Builder()
                                         .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
@@ -180,15 +354,50 @@
                                         .setRssi(filterResult.getRssi())
                                         .setAction(filterResult.getIntent())
                                         .setPublicCredential(publicCredential)
+                                        .setPresenceDevice(presenceDeviceBuilder.build())
+                                        .setEncryptionKeyTag(encryptedMetaDataTag)
                                         .build();
                         mExecutor.execute(() -> mListener.onNearbyDeviceDiscovered(device));
                     }
-                } catch (InvalidProtocolBufferException e) {
-                    Log.e(
-                            TAG,
-                            String.format("Failed to decode the filter result %s", e.toString()));
+                } catch (Exception e) {
+                    Log.e(TAG, String.format("Failed to decode the filter result %s", e));
                 }
             }
         }
+
+        private void addDataElementsToPresenceDevice(Blefilter.DataElement element,
+                PresenceDevice.Builder presenceDeviceBuilder) {
+            int endIndex = element.hasValueLength() ? element.getValueLength() :
+                    element.getValue().size();
+            switch (element.getKey()) {
+                case DE_FAST_PAIR_ACCOUNT_KEY:
+                    presenceDeviceBuilder.addExtendedProperty(
+                            new DataElement(DataElement.DataType.ACCOUNT_KEY_DATA,
+                                    element.getValue().substring(0, endIndex).toByteArray()));
+                    break;
+                case DE_CONNECTION_STATUS:
+                    presenceDeviceBuilder.addExtendedProperty(
+                            new DataElement(DataElement.DataType.CONNECTION_STATUS,
+                                    element.getValue().substring(0, endIndex).toByteArray()));
+                    break;
+                case DE_BATTERY_STATUS:
+                    presenceDeviceBuilder.addExtendedProperty(
+                            new DataElement(DataElement.DataType.BATTERY,
+                                    element.getValue().substring(0, endIndex).toByteArray()));
+                    break;
+                default:
+                    if (mNearbyConfiguration.isTestAppSupported()
+                            && DataElement.isTestDeType(element.getKey().getNumber())) {
+                        presenceDeviceBuilder.addExtendedProperty(
+                                new DataElement(Arrays.stream(
+                                                Blefilter.DataElement.ElementType.values())
+                                        .filter(p -> p.getNumber() == element.getKey().getNumber())
+                                        .findFirst()
+                                        .get().getNumber(),
+                                        element.getValue().substring(0, endIndex).toByteArray()));
+                    }
+                    break;
+            }
+        }
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index bdeab51..41d5686 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -23,8 +23,10 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.nearby.DataElement;
 import android.nearby.IScanListener;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.NearbyManager;
 import android.nearby.PresenceScanFilter;
 import android.nearby.ScanFilter;
 import android.nearby.ScanRequest;
@@ -33,6 +35,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.metrics.NearbyMetrics;
 import com.android.server.nearby.presence.PresenceDiscoveryResult;
@@ -54,7 +57,9 @@
     protected final Object mLock = new Object();
     private final Context mContext;
     private final BleDiscoveryProvider mBleDiscoveryProvider;
-    @Nullable private final ChreDiscoveryProvider mChreDiscoveryProvider;
+    @VisibleForTesting
+    @Nullable
+    final ChreDiscoveryProvider mChreDiscoveryProvider;
     private @ScanRequest.ScanMode int mScanMode;
     private final Injector mInjector;
 
@@ -92,10 +97,9 @@
                                                     scanFilter.getType()
                                                             == SCAN_TYPE_NEARBY_PRESENCE)
                                     .collect(Collectors.toList());
-                    Log.i(
-                            TAG,
-                            String.format("match with filters size: %d", presenceFilters.size()));
                     if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
+                        Log.d(TAG, "presence filter does not match for "
+                                + "the scanned Presence Device");
                         continue;
                     }
                 }
@@ -119,40 +123,73 @@
         Executor executor = Executors.newSingleThreadExecutor();
         mChreDiscoveryProvider =
                 new ChreDiscoveryProvider(
-                        mContext, new ChreCommunication(injector, executor), executor);
+                        mContext, new ChreCommunication(injector, mContext, executor), executor);
         mScanTypeScanListenerRecordMap = new HashMap<>();
         mInjector = injector;
     }
 
+    @VisibleForTesting
+    DiscoveryProviderManager(Context context, Injector injector,
+            BleDiscoveryProvider bleDiscoveryProvider,
+            ChreDiscoveryProvider chreDiscoveryProvider,
+            Map<IBinder, ScanListenerRecord> scanTypeScanListenerRecordMap) {
+        mContext = context;
+        mInjector = injector;
+        mBleDiscoveryProvider = bleDiscoveryProvider;
+        mChreDiscoveryProvider = chreDiscoveryProvider;
+        mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap;
+    }
+
+    /** Called after boot completed. */
+    public void init() {
+        if (mInjector.getContextHubManager() != null) {
+            mChreDiscoveryProvider.init();
+        }
+        mChreDiscoveryProvider.getController().setListener(this);
+    }
+
     /**
      * Registers the listener in the manager and starts scan according to the requested scan mode.
      */
-    public boolean registerScanListener(ScanRequest scanRequest, IScanListener listener,
+    @NearbyManager.ScanStatus
+    public int registerScanListener(ScanRequest scanRequest, IScanListener listener,
             CallerIdentity callerIdentity) {
         synchronized (mLock) {
+            ScanListenerDeathRecipient deathRecipient = (listener != null)
+                    ? new ScanListenerDeathRecipient(listener) : null;
             IBinder listenerBinder = listener.asBinder();
+            if (listenerBinder != null && deathRecipient != null) {
+                try {
+                    listenerBinder.linkToDeath(deathRecipient, 0);
+                } catch (RemoteException e) {
+                    throw new IllegalArgumentException("Can't link to scan listener's death");
+                }
+            }
             if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
                 ScanRequest savedScanRequest =
                         mScanTypeScanListenerRecordMap.get(listenerBinder).getScanRequest();
                 if (scanRequest.equals(savedScanRequest)) {
                     Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
-                    return true;
+                    return NearbyManager.ScanStatus.SUCCESS;
                 }
             }
             ScanListenerRecord scanListenerRecord =
-                    new ScanListenerRecord(scanRequest, listener, callerIdentity);
+                    new ScanListenerRecord(scanRequest, listener, callerIdentity, deathRecipient);
             mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
 
-            if (!startProviders(scanRequest)) {
-                return false;
+            Boolean started = startProviders(scanRequest);
+            if (started == null) {
+                return NearbyManager.ScanStatus.UNKNOWN;
             }
-
+            if (!started) {
+                return NearbyManager.ScanStatus.ERROR;
+            }
             NearbyMetrics.logScanStarted(scanListenerRecord.hashCode(), scanRequest);
             if (mScanMode < scanRequest.getScanMode()) {
                 mScanMode = scanRequest.getScanMode();
                 invalidateProviderScanMode();
             }
-            return true;
+            return NearbyManager.ScanStatus.SUCCESS;
         }
     }
 
@@ -172,6 +209,10 @@
 
             ScanListenerRecord removedRecord =
                     mScanTypeScanListenerRecordMap.remove(listenerBinder);
+            ScanListenerDeathRecipient deathRecipient = removedRecord.getDeathRecipient();
+            if (listenerBinder != null && deathRecipient != null) {
+                listenerBinder.unlinkToDeath(removedRecord.getDeathRecipient(), 0);
+            }
             Log.v(TAG, "DiscoveryProviderManager unregistered scan listener.");
             NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest());
             if (mScanTypeScanListenerRecordMap.isEmpty()) {
@@ -203,34 +244,91 @@
         }
     }
 
-    // Returns false when fail to start all the providers. Returns true if any one of the provider
-    // starts successfully.
-    private boolean startProviders(ScanRequest scanRequest) {
-        if (scanRequest.isBleEnabled()) {
-            if (mChreDiscoveryProvider.available()
-                    && scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
-                startChreProvider();
-            } else {
-                startBleProvider(scanRequest);
+    /**
+     * @return {@code null} when all providers are initializing
+     * {@code false} when fail to start all the providers
+     * {@code true} when any one of the provider starts successfully
+     */
+    @VisibleForTesting
+    @Nullable
+    Boolean startProviders(ScanRequest scanRequest) {
+        if (!scanRequest.isBleEnabled()) {
+            Log.w(TAG, "failed to start any provider because client disabled BLE");
+            return false;
+        }
+        List<ScanFilter> scanFilters = getPresenceScanFilters();
+        boolean chreOnly = isChreOnly(scanFilters);
+        Boolean chreAvailable = mChreDiscoveryProvider.available();
+        if (chreAvailable == null) {
+            if (chreOnly) {
+                Log.w(TAG, "client wants CHRE only and Nearby service is still querying CHRE"
+                        + " status");
+                return null;
             }
+            startBleProvider(scanFilters);
             return true;
         }
+
+        if (!chreAvailable) {
+            if (chreOnly) {
+                Log.w(TAG, "failed to start any provider because client wants CHRE only and CHRE"
+                        + " is not available");
+                return false;
+            }
+            startBleProvider(scanFilters);
+            return true;
+        }
+
+        if (scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
+            startChreProvider(scanFilters);
+            return true;
+        }
+
+        startBleProvider(scanFilters);
+        return true;
+    }
+
+    private static boolean isChreOnly(List<ScanFilter> scanFilters) {
+        for (ScanFilter scanFilter : scanFilters) {
+            List<DataElement> dataElements =
+                    ((PresenceScanFilter) scanFilter).getExtendedProperties();
+            for (DataElement dataElement : dataElements) {
+                if (dataElement.getKey() != DataElement.DataType.SCAN_MODE) {
+                    continue;
+                }
+                byte[] scanModeValue = dataElement.getValue();
+                if (scanModeValue == null || scanModeValue.length == 0) {
+                    break;
+                }
+                if (Byte.toUnsignedInt(scanModeValue[0]) == ScanRequest.SCAN_MODE_CHRE_ONLY) {
+                    return true;
+                }
+            }
+
+        }
         return false;
     }
 
-    private void startBleProvider(ScanRequest scanRequest) {
+    private void startBleProvider(List<ScanFilter> scanFilters) {
         if (!mBleDiscoveryProvider.getController().isStarted()) {
             Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
-            mBleDiscoveryProvider.getController().start();
             mBleDiscoveryProvider.getController().setListener(this);
-            mBleDiscoveryProvider.getController().setProviderScanMode(scanRequest.getScanMode());
+            mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+            mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
+            mBleDiscoveryProvider.getController().start();
         }
     }
 
-    private void startChreProvider() {
+    @VisibleForTesting
+    void startChreProvider(List<ScanFilter> scanFilters) {
         Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
+        mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
+        mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+        mChreDiscoveryProvider.getController().start();
+    }
+
+    private List<ScanFilter> getPresenceScanFilters() {
         synchronized (mLock) {
-            mChreDiscoveryProvider.getController().setListener(this);
             List<ScanFilter> scanFilters = new ArrayList();
             for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
                 ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
@@ -242,9 +340,7 @@
                                 .collect(Collectors.toList());
                 scanFilters.addAll(presenceFilters);
             }
-            mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
-            mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
-            mChreDiscoveryProvider.getController().start();
+            return scanFilters;
         }
     }
 
@@ -257,11 +353,13 @@
         mBleDiscoveryProvider.getController().stop();
     }
 
-    private void stopChreProvider() {
+    @VisibleForTesting
+    protected void stopChreProvider() {
         mChreDiscoveryProvider.getController().stop();
     }
 
-    private void invalidateProviderScanMode() {
+    @VisibleForTesting
+    void invalidateProviderScanMode() {
         if (mBleDiscoveryProvider.getController().isStarted()) {
             mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
         } else {
@@ -272,7 +370,8 @@
         }
     }
 
-    private static boolean presenceFilterMatches(
+    @VisibleForTesting
+    static boolean presenceFilterMatches(
             NearbyDeviceParcelable device, List<ScanFilter> scanFilters) {
         if (scanFilters.isEmpty()) {
             return true;
@@ -287,7 +386,22 @@
         return false;
     }
 
-    private static class ScanListenerRecord {
+    class ScanListenerDeathRecipient implements IBinder.DeathRecipient {
+        public IScanListener listener;
+
+        ScanListenerDeathRecipient(IScanListener listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.d(TAG, "Binder is dead - unregistering scan listener");
+            unregisterScanListener(listener);
+        }
+    }
+
+    @VisibleForTesting
+    static class ScanListenerRecord {
 
         private final ScanRequest mScanRequest;
 
@@ -295,11 +409,14 @@
 
         private final CallerIdentity mCallerIdentity;
 
+        private final ScanListenerDeathRecipient mDeathRecipient;
+
         ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener,
-                CallerIdentity callerIdentity) {
+                CallerIdentity callerIdentity, ScanListenerDeathRecipient deathRecipient) {
             mScanListener = iScanListener;
             mScanRequest = scanRequest;
             mCallerIdentity = callerIdentity;
+            mDeathRecipient = deathRecipient;
         }
 
         IScanListener getScanListener() {
@@ -314,6 +431,10 @@
             return mCallerIdentity;
         }
 
+        ScanListenerDeathRecipient getDeathRecipient()  {
+            return mDeathRecipient;
+        }
+
         @Override
         public boolean equals(Object other) {
             if (other instanceof ScanListenerRecord) {
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
index 0f99a2f..d925f07 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -30,7 +30,7 @@
 
 import androidx.annotation.WorkerThread;
 
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
 
 import java.util.ArrayList;
@@ -80,6 +80,11 @@
         }
     }
 
+    @VisibleForTesting
+    void setProxyDataProvider(ProxyFastPairDataProvider proxyFastPairDataProvider) {
+        this.mProxyFastPairDataProvider = proxyFastPairDataProvider;
+    }
+
     /**
      * Loads FastPairAntispoofKeyDeviceMetadata.
      *
@@ -136,14 +141,6 @@
     }
 
     /**
-     * Get recognized device from bloom filter.
-     */
-    public Data.FastPairDeviceWithAccountKey getRecognizedDevice(BloomFilter bloomFilter,
-            byte[] salt) {
-        return Data.FastPairDeviceWithAccountKey.newBuilder().build();
-    }
-
-    /**
      * Loads FastPair device accountKeys for a given account, but not other detailed fields.
      *
      * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
diff --git a/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java
index 599843c..35251d8 100644
--- a/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java
+++ b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java
@@ -45,4 +45,11 @@
         }
         return result;
     }
+
+    /**
+     * @return true when the array is null or length is 0
+     */
+    public static boolean isEmpty(byte[] bytes) {
+        return bytes == null || bytes.length == 0;
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/util/Clock.java b/nearby/service/java/com/android/server/nearby/util/Clock.java
new file mode 100644
index 0000000..037b6f9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/Clock.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import android.os.SystemClock;
+
+/** Wrapper interface for time operations. Allows replacement of clock operations for testing. */
+public interface Clock {
+
+    /**
+     * Get the current time of the clock in milliseconds.
+     *
+     * @return Current time in milliseconds.
+     */
+    long currentTimeMillis();
+
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @return Current time since boot in milliseconds.
+     */
+    long elapsedRealtime();
+
+    /**
+     * Returns the current timestamp of the most precise timer available on the local system, in
+     * nanoseconds.
+     *
+     * @return Current time in nanoseconds.
+     */
+    long nanoTime();
+
+    /**
+     * Returns the time spent in the current thread, in milliseconds
+     *
+     * @return Thread time in milliseconds.
+     */
+    @SuppressWarnings("StaticOrDefaultInterfaceMethod")
+    default long currentThreadTimeMillis() {
+        return SystemClock.currentThreadTimeMillis();
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
index 8bb83e9..12bf384 100644
--- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java
+++ b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
@@ -38,11 +38,12 @@
      */
     public static ScanFastPairStoreItem toScanFastPairStoreItem(
             GetObservedDeviceResponse observedDeviceResponse,
-            @NonNull String bleAddress, @Nullable String account) {
+            @NonNull String bleAddress, @NonNull String modelId, @Nullable String account) {
         Device device = observedDeviceResponse.getDevice();
         String deviceName = device.getName();
         return ScanFastPairStoreItem.newBuilder()
                 .setAddress(bleAddress)
+                .setModelId(modelId)
                 .setActionUrl(device.getIntentUri())
                 .setDeviceName(deviceName)
                 .setIconPng(observedDeviceResponse.getImage())
@@ -57,11 +58,9 @@
      */
     public static String toString(ScanFastPairStoreItem item) {
         return "ScanFastPairStoreItem=[address:" + item.getAddress()
-                + ", actionUr:" + item.getActionUrl()
+                + ", actionUrl:" + item.getActionUrl()
                 + ", deviceName:" + item.getDeviceName()
-                + ", iconPng:" + item.getIconPng()
                 + ", iconFifeUrl:" + item.getIconFifeUrl()
-                + ", antiSpoofingKeyPair:" + item.getAntiSpoofingPublicKey()
                 + ", fastPairStrings:" + toString(item.getFastPairStrings())
                 + "]";
     }
diff --git a/nearby/service/java/com/android/server/nearby/util/DefaultClock.java b/nearby/service/java/com/android/server/nearby/util/DefaultClock.java
new file mode 100644
index 0000000..61998e9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/DefaultClock.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import android.os.SystemClock;
+
+/** Default implementation of Clock. Instances of this class handle time operations. */
+public class DefaultClock implements Clock {
+
+    private static final DefaultClock sInstance = new DefaultClock();
+
+    /** Returns an instance of DefaultClock. */
+    public static Clock getsInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public long currentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    @Override
+    public long elapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    @Override
+    public long nanoTime() {
+        return System.nanoTime();
+    }
+
+    @Override
+    public long currentThreadTimeMillis() {
+        return SystemClock.currentThreadTimeMillis();
+    }
+
+    public DefaultClock() {}
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
deleted file mode 100644
index 6021ff6..0000000
--- a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2022 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.util;
-
-import android.annotation.Nullable;
-import android.bluetooth.le.ScanRecord;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import com.android.server.nearby.common.ble.BleFilter;
-import com.android.server.nearby.common.ble.BleRecord;
-
-import java.util.Arrays;
-
-/**
- * Parses Fast Pair information out of {@link BleRecord}s.
- *
- * <p>There are 2 different packet formats that are supported, which is used can be determined by
- * packet length:
- *
- * <p>For 3-byte packets, the full packet is the model ID.
- *
- * <p>For all other packets, the first byte is the header, followed by the model ID, followed by
- * zero or more extra fields. Each field has its own header byte followed by the field value. The
- * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and
- * each extra field header is 0bLLLLTTTT (L = field length, T = field type).
- */
-public class FastPairDecoder {
-
-    private static final int FIELD_TYPE_BLOOM_FILTER = 0;
-    private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1;
-    private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2;
-    private static final int FIELD_TYPE_BATTERY = 3;
-    private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4;
-    public static final int FIELD_TYPE_CONNECTION_STATE = 5;
-    private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6;
-
-
-    /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */
-    private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID =
-            ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB");
-
-    /** The filter you use to scan for Fast Pair BLE advertisements. */
-    public static final BleFilter FILTER =
-            new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID,
-                    new byte[0]).build();
-
-    // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly
-    // without needing worry about signing errors.
-    private static final int HEADER_VERSION_BITMASK = 0b11100000;
-    private static final int HEADER_LENGTH_BITMASK = 0b00011110;
-    private static final int HEADER_VERSION_OFFSET = 5;
-    private static final int HEADER_LENGTH_OFFSET = 1;
-
-    private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000;
-    private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111;
-    private static final int EXTRA_FIELD_LENGTH_OFFSET = 4;
-    private static final int EXTRA_FIELD_TYPE_OFFSET = 0;
-
-    private static final int MIN_ID_LENGTH = 3;
-    private static final int MAX_ID_LENGTH = 14;
-    private static final int HEADER_INDEX = 0;
-    private static final int HEADER_LENGTH = 1;
-    private static final int FIELD_HEADER_LENGTH = 1;
-
-    // Not using java.util.IllegalFormatException because it is unchecked.
-    private static class IllegalFormatException extends Exception {
-        private IllegalFormatException(String message) {
-            super(message);
-        }
-    }
-
-    /**
-     * Gets model id data from broadcast
-     */
-    @Nullable
-    public static byte[] getModelId(@Nullable byte[] serviceData) {
-        if (serviceData == null) {
-            return null;
-        }
-
-        if (serviceData.length >= MIN_ID_LENGTH) {
-            if (serviceData.length == MIN_ID_LENGTH) {
-                // If the length == 3, all bytes are the ID. See flag docs for more about
-                // endianness.
-                return serviceData;
-            } else {
-                // Otherwise, the first byte is a header which contains the length of the big-endian
-                // model ID that follows. The model ID will be trimmed if it contains leading zeros.
-                int idIndex = 1;
-                int end = idIndex + getIdLength(serviceData);
-                while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) {
-                    idIndex++;
-                }
-                return Arrays.copyOfRange(serviceData, idIndex, end);
-            }
-        }
-        return null;
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(BleRecord bleRecord) {
-        return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(ScanRecord scanRecord) {
-        return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the bloom filter from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilter(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER);
-    }
-
-    /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilterSalt(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT);
-    }
-
-    /**
-     * Gets the suppress notification with bloom filter from the extra fields if available,
-     * otherwise returns null.
-     */
-    @Nullable
-    public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
-    }
-
-    /**
-     * Get random resolvableData
-     */
-    @Nullable
-    public static byte[] getRandomResolvableData(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA);
-    }
-
-    @Nullable
-    private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) {
-        if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) {
-            return null;
-        }
-        try {
-            return getExtraFields(serviceData).get(fieldId);
-        } catch (IllegalFormatException e) {
-            return null;
-        }
-    }
-
-    /** Gets extra field data at the end of the packet, defined by the extra field header. */
-    private static SparseArray<byte[]> getExtraFields(byte[] serviceData)
-            throws IllegalFormatException {
-        SparseArray<byte[]> extraFields = new SparseArray<>();
-        if (getVersion(serviceData) != 0) {
-            return extraFields;
-        }
-        int headerIndex = getFirstExtraFieldHeaderIndex(serviceData);
-        while (headerIndex < serviceData.length) {
-            int length = getExtraFieldLength(serviceData, headerIndex);
-            int index = headerIndex + FIELD_HEADER_LENGTH;
-            int type = getExtraFieldType(serviceData, headerIndex);
-            int end = index + length;
-            if (extraFields.get(type) == null) {
-                if (end <= serviceData.length) {
-                    extraFields.put(type, Arrays.copyOfRange(serviceData, index, end));
-                } else {
-                    throw new IllegalFormatException(
-                            "Invalid length, " + end + " is longer than service data size "
-                                    + serviceData.length);
-                }
-            }
-            headerIndex = end;
-        }
-        return extraFields;
-    }
-
-    /** Checks whether or not a valid ID is included in the service data packet. */
-    public static boolean hasBeaconIdBytes(BleRecord bleRecord) {
-        byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-        return checkModelId(serviceData);
-    }
-
-    /** Check whether byte array is FastPair model id or not. */
-    public static boolean checkModelId(@Nullable byte[] scanResult) {
-        return scanResult != null
-                // The 3-byte format has no header byte (all bytes are the ID).
-                && (scanResult.length == MIN_ID_LENGTH
-                // Header byte exists. We support only format version 0. (A different version
-                // indicates
-                // a breaking change in the format.)
-                || (scanResult.length > MIN_ID_LENGTH
-                && getVersion(scanResult) == 0
-                && isIdLengthValid(scanResult)));
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(BleRecord bleRecord) {
-        return (getBloomFilter(getServiceDataArray(bleRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null);
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(ScanRecord scanRecord) {
-        return (getBloomFilter(getServiceDataArray(scanRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null);
-    }
-
-    private static int getVersion(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? 0
-                : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET;
-    }
-
-    private static int getIdLength(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? MIN_ID_LENGTH
-                : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET;
-    }
-
-    private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) {
-        return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData);
-    }
-
-    private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK)
-                >> EXTRA_FIELD_LENGTH_OFFSET;
-    }
-
-    private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET;
-    }
-
-    private static boolean isIdLengthValid(byte[] serviceData) {
-        int idLength = getIdLength(serviceData);
-        return MIN_ID_LENGTH <= idLength
-                && idLength <= MAX_ID_LENGTH
-                && idLength + HEADER_LENGTH <= serviceData.length;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java b/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java
new file mode 100644
index 0000000..3c5132d
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Class for encryption/decryption functionality. */
+public abstract class Cryptor {
+
+    /** AES only supports key sizes of 16, 24 or 32 bytes. */
+    static final int AUTHENTICITY_KEY_BYTE_SIZE = 16;
+
+    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+    /**
+     * Encrypt the provided data blob.
+     *
+     * @param data data blob to be encrypted.
+     * @param salt used for IV
+     * @param secretKeyBytes secrete key accessed from credentials
+     * @return encrypted data, {@code null} if failed to encrypt.
+     */
+    @Nullable
+    public byte[] encrypt(byte[] data, byte[] salt, byte[] secretKeyBytes) {
+        return data;
+    }
+
+    /**
+     * Decrypt the original data blob from the provided byte array.
+     *
+     * @param encryptedData data blob to be decrypted.
+     * @param salt used for IV
+     * @param secretKeyBytes secrete key accessed from credentials
+     * @return decrypted data, {@code null} if failed to decrypt.
+     */
+    @Nullable
+    public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] secretKeyBytes) {
+        return encryptedData;
+    }
+
+    /**
+     * Generates a digital signature for the data.
+     *
+     * @return signature {@code null} if failed to sign
+     */
+    @Nullable
+    public byte[] sign(byte[] data, byte[] key) {
+        return new byte[0];
+    }
+
+    /**
+     * Verifies the signature generated by data and key, with the original signed data
+     */
+    public boolean verify(byte[] data, byte[] key, byte[] signature) {
+        return true;
+    }
+
+    /**
+     * @return length of the signature generated
+     */
+    public int getSignatureLength() {
+        return 0;
+    }
+
+    /**
+     * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
+     * given size.
+     */
+    // Based on google3/third_party/tink/java/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
+    @Nullable
+    static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
+        Mac mac;
+        try {
+            mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e);
+            return null;
+        }
+
+        if (size > 255 * mac.getMacLength()) {
+            Log.w(TAG, "Size too large.");
+            return null;
+        }
+
+        if (salt.length == 0) {
+            Log.w(TAG, "Salt cannot be empty.");
+            return null;
+        }
+
+        try {
+            mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] prk = mac.doFinal(ikm);
+        byte[] result = new byte[size];
+        try {
+            mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] digest = new byte[0];
+        int ctr = 1;
+        int pos = 0;
+        while (true) {
+            mac.update(digest);
+            mac.update((byte) ctr);
+            digest = mac.doFinal();
+            if (pos + digest.length < size) {
+                System.arraycopy(digest, 0, result, pos, digest.length);
+                pos += digest.length;
+                ctr++;
+            } else {
+                System.arraycopy(digest, 0, result, pos, size - pos);
+                break;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java
new file mode 100644
index 0000000..1c0ec9e
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A Cryptor that returns the original data without actual encryption
+ */
+public class CryptorImpFake extends Cryptor {
+    // Lazily instantiated when {@link #getInstance()} is called.
+    @Nullable
+    private static CryptorImpFake sCryptor;
+
+    /** Returns an instance of CryptorImpFake. */
+    public static CryptorImpFake getInstance() {
+        if (sCryptor == null) {
+            sCryptor = new CryptorImpFake();
+        }
+        return sCryptor;
+    }
+
+    private CryptorImpFake() {
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java
new file mode 100644
index 0000000..b0e19b4
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for identity
+ * encryption and decryption.
+ */
+public class CryptorImpIdentityV1 extends Cryptor {
+
+    // 3 16 byte arrays known by both the encryptor and decryptor.
+    private static final byte[] EK_IV =
+            new byte[] {14, -123, -39, 42, 109, 127, 83, 27, 27, 11, 91, -38, 92, 17, -84, 66};
+    private static final byte[] ESALT_IV =
+            new byte[] {46, 83, -19, 10, -127, -31, -31, 12, 31, 76, 63, -9, 33, -66, 15, -10};
+    private static final byte[] KTAG_IV =
+            {-22, -83, -6, 67, 16, -99, -13, -9, 8, -3, -16, 37, -75, 47, 1, -56};
+
+    /** Length of encryption key required by AES/GCM encryption. */
+    private static final int ENCRYPTION_KEY_SIZE = 32;
+
+    /** Length of salt required by AES/GCM encryption. */
+    private static final int AES_CTR_IV_SIZE = 16;
+
+    /** Length HMAC tag */
+    public static final int HMAC_TAG_SIZE = 8;
+
+    /**
+     * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
+     */
+    private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+
+    @VisibleForTesting
+    static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
+
+    // Lazily instantiated when {@link #getInstance()} is called.
+    @Nullable private static CryptorImpIdentityV1 sCryptor;
+
+    /** Returns an instance of CryptorImpIdentityV1. */
+    public static CryptorImpIdentityV1 getInstance() {
+        if (sCryptor == null) {
+            sCryptor = new CryptorImpIdentityV1();
+        }
+        return sCryptor;
+    }
+
+    @Nullable
+    @Override
+    public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Encrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+        byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE);
+        if (esalt == null) {
+            Log.e(TAG, "Failed to generate salt.");
+            return null;
+        }
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(esalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+        try {
+            return cipher.doFinal(data);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Decrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to get cipher instance.", e);
+            return null;
+        }
+        byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE);
+        if (esalt == null) {
+            return null;
+        }
+        try {
+            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(esalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+
+        try {
+            return cipher.doFinal(encryptedData);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
+            return null;
+        }
+    }
+
+    /**
+     * Generates a digital signature for the data.
+     *
+     * @return signature {@code null} if failed to sign
+     */
+    @Nullable
+    @Override
+    public byte[] sign(byte[] data, byte[] salt) {
+        if (data == null) {
+            Log.e(TAG, "Not generate HMAC tag because of invalid data input.");
+            return null;
+        }
+
+        // Generates a 8 bytes HMAC tag
+        return Cryptor.computeHkdf(data, salt, HMAC_TAG_SIZE);
+    }
+
+    /**
+     * Generates a digital signature for the data.
+     * Uses KTAG_IV as salt value.
+     */
+    @Nullable
+    public byte[] sign(byte[] data) {
+        // Generates a 8 bytes HMAC tag
+        return sign(data, KTAG_IV);
+    }
+
+    @Override
+    public boolean verify(byte[] data, byte[] key, byte[] signature) {
+        return Arrays.equals(sign(data, key), signature);
+    }
+
+    /**
+     * Verifies the signature generated by data and key, with the original signed data. Uses
+     * KTAG_IV as salt value.
+     */
+    public boolean verify(byte[] data, byte[] signature) {
+        return verify(data, KTAG_IV, signature);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java
new file mode 100644
index 0000000..15073fb
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for encryption and decryption.
+ */
+public class CryptorImpV1 extends Cryptor {
+
+    /**
+     * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
+     */
+    private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+
+    @VisibleForTesting
+    static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
+
+    /** Length of encryption key required by AES/GCM encryption. */
+    private static final int ENCRYPTION_KEY_SIZE = 32;
+
+    /** Length of salt required by AES/GCM encryption. */
+    private static final int AES_CTR_IV_SIZE = 16;
+
+    /** Length HMAC tag */
+    public static final int HMAC_TAG_SIZE = 16;
+
+    // 3 16 byte arrays known by both the encryptor and decryptor.
+    private static final byte[] AK_IV =
+            new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78};
+    private static final byte[] ASALT_IV =
+            new byte[] {111, 48, -83, -79, -10, -102, -16, 73, 43, 55, 102, -127, 58, -19, -113, 4};
+    private static final byte[] HK_IV =
+            new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78};
+
+    // Lazily instantiated when {@link #getInstance()} is called.
+    @Nullable private static CryptorImpV1 sCryptor;
+
+    /** Returns an instance of CryptorImpV1. */
+    public static CryptorImpV1 getInstance() {
+        if (sCryptor == null) {
+            sCryptor = new CryptorImpV1();
+        }
+        return sCryptor;
+    }
+
+    private CryptorImpV1() {
+    }
+
+    @Nullable
+    @Override
+    public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Encrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+        byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
+        if (asalt == null) {
+            Log.e(TAG, "Failed to generate salt.");
+            return null;
+        }
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(asalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+        try {
+            return cipher.doFinal(data);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Decrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to get cipher instance.", e);
+            return null;
+        }
+        byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
+        if (asalt == null) {
+            return null;
+        }
+        try {
+            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(asalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+
+        try {
+            return cipher.doFinal(encryptedData);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
+            return null;
+        }
+    }
+
+    @Override
+    @Nullable
+    public byte[] sign(byte[] data, byte[] key) {
+        return generateHmacTag(data, key);
+    }
+
+    @Override
+    public int getSignatureLength() {
+        return HMAC_TAG_SIZE;
+    }
+
+    @Override
+    public boolean verify(byte[] data, byte[] key, byte[] signature) {
+        return Arrays.equals(sign(data, key), signature);
+    }
+
+    /** Generates a 16 bytes HMAC tag. This is used for decryptor to verify if the computed HMAC tag
+     * is equal to HMAC tag in advertisement to see data integrity. */
+    @Nullable
+    @VisibleForTesting
+    byte[] generateHmacTag(byte[] data, byte[] authenticityKey) {
+        if (data == null || authenticityKey == null) {
+            Log.e(TAG, "Not generate HMAC tag because of invalid data input.");
+            return null;
+        }
+
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.e(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes HMAC key from authenticity_key
+        byte[] hmacKey = Cryptor.computeHkdf(authenticityKey, HK_IV, AES_CTR_IV_SIZE);
+        if (hmacKey == null) {
+            Log.e(TAG, "Failed to generate HMAC key.");
+            return null;
+        }
+
+        // Generates a 16 bytes HMAC tag from authenticity_key
+        return Cryptor.computeHkdf(data, hmacKey, HMAC_TAG_SIZE);
+    }
+}
diff --git a/nearby/service/proto/src/presence/blefilter.proto b/nearby/service/proto/src/presence/blefilter.proto
index 9f75d34..9b760c1 100644
--- a/nearby/service/proto/src/presence/blefilter.proto
+++ b/nearby/service/proto/src/presence/blefilter.proto
@@ -47,6 +47,7 @@
   optional bytes metadata_encryption_key_tag = 2;
 }
 
+// Public credential returned in BleFilterResult.
 message PublicCredential {
   optional bytes secret_id = 1;
   optional bytes authenticity_key = 2;
@@ -55,6 +56,30 @@
   optional bytes encrypted_metadata_tag = 5;
 }
 
+message DataElement {
+  enum ElementType {
+    option allow_alias = true;
+
+    DE_NONE = 0;
+    DE_FAST_PAIR_ACCOUNT_KEY = 9;
+    DE_CONNECTION_STATUS = 10;
+    DE_BATTERY_STATUS = 11;
+    // Reserves Test DEs.
+    DE_TEST_BEGIN = 256;
+    DE_TEST_1 = 256;
+    DE_TEST_2 = 257;
+    DE_TEST_3 = 258;
+    DE_TEST_4 = 259;
+    DE_TEST_5 = 260;
+    DE_TEST_END = 260;
+  }
+
+  optional ElementType key = 1;
+  optional bytes value = 2;
+  optional uint32 value_length = 3;
+}
+
+// A single filter used to filter BLE events.
 message BleFilter {
   optional uint32 id = 1;  // Required, unique id of this filter.
   // Maximum delay to notify the client after an event occurs.
@@ -71,7 +96,9 @@
   // the period of latency defined above.
   optional float distance_m = 7;
   // Used to verify the list of trusted devices.
-  repeated PublicateCertificate certficate = 8;
+  repeated PublicateCertificate certificate = 8;
+  // Data Elements for extended properties.
+  repeated DataElement data_element = 9;
 }
 
 message BleFilters {
@@ -80,14 +107,33 @@
 
 // FilterResult is returned to host when a BLE event matches a Filter.
 message BleFilterResult {
+  enum ResultType {
+    RESULT_NONE = 0;
+    RESULT_PRESENCE = 1;
+    RESULT_FAST_PAIR = 2;
+  }
+
   optional uint32 id = 1;  // id of the matched Filter.
-  optional uint32 tx_power = 2;
-  optional uint32 rssi = 3;
+  optional int32 tx_power = 2;
+  optional int32 rssi = 3;
   optional uint32 intent = 4;
   optional bytes bluetooth_address = 5;
   optional PublicCredential public_credential = 6;
+  repeated DataElement data_element = 7;
+  optional bytes ble_service_data = 8;
+  optional ResultType result_type = 9;
 }
 
 message BleFilterResults {
   repeated BleFilterResult result = 1;
 }
+
+message BleConfig {
+  // True to start BLE scan. Otherwise, stop BLE scan.
+  optional bool start_scan = 1;
+  // True when screen is turned on. Otherwise, set to false when screen is
+  // turned off.
+  optional bool screen_on = 2;
+  // Fast Pair cache expires after this time period.
+  optional uint64 fast_pair_cache_expire_time_sec = 3;
+}
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 0410cd5..a61d180 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -41,7 +41,6 @@
         "mts-tethering",
     ],
     certificate: "platform",
-    platform_apis: true,
     sdk_version: "module_current",
     min_sdk_version: "30",
     target_sdk_version: "32",
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
index aacb6d8..a2da967 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
@@ -42,7 +42,6 @@
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testBuilder() {
         CredentialElement element = new CredentialElement(KEY, VALUE);
-
         assertThat(element.getKey()).isEqualTo(KEY);
         assertThat(Arrays.equals(element.getValue(), VALUE)).isTrue();
     }
@@ -58,9 +57,31 @@
         CredentialElement elementFromParcel = element.CREATOR.createFromParcel(
                 parcel);
         parcel.recycle();
-
         assertThat(elementFromParcel.getKey()).isEqualTo(KEY);
         assertThat(Arrays.equals(elementFromParcel.getValue(), VALUE)).isTrue();
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        CredentialElement element = new CredentialElement(KEY, VALUE);
+        assertThat(element.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEqual() {
+        CredentialElement element1 = new CredentialElement(KEY, VALUE);
+        CredentialElement element2 = new CredentialElement(KEY, VALUE);
+        assertThat(element1.equals(element2)).isTrue();
+        assertThat(element1.hashCode()).isEqualTo(element2.hashCode());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        CredentialElement [] elements =
+                CredentialElement.CREATOR.newArray(2);
+        assertThat(elements.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
index 3654d0d..84814ae 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
@@ -16,6 +16,13 @@
 
 package android.nearby.cts;
 
+import static android.nearby.DataElement.DataType.PRIVATE_IDENTITY;
+import static android.nearby.DataElement.DataType.PROVISIONED_IDENTITY;
+import static android.nearby.DataElement.DataType.PUBLIC_IDENTITY;
+import static android.nearby.DataElement.DataType.SALT;
+import static android.nearby.DataElement.DataType.TRUSTED_IDENTITY;
+import static android.nearby.DataElement.DataType.TX_POWER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.nearby.DataElement;
@@ -31,7 +38,6 @@
 
 import java.util.Arrays;
 
-
 @RunWith(AndroidJUnit4.class)
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class DataElementTest {
@@ -63,4 +69,59 @@
         assertThat(elementFromParcel.getKey()).isEqualTo(KEY);
         assertThat(Arrays.equals(elementFromParcel.getValue(), VALUE)).isTrue();
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        DataElement dataElement = new DataElement(KEY, VALUE);
+        assertThat(dataElement.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        DataElement[] elements =
+                DataElement.CREATOR.newArray(2);
+        assertThat(elements.length).isEqualTo(2);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEquals() {
+        DataElement dataElement = new DataElement(KEY, VALUE);
+        DataElement dataElement2 = new DataElement(KEY, VALUE);
+
+        assertThat(dataElement.equals(dataElement2)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsIdentity() {
+        DataElement privateIdentity = new DataElement(PRIVATE_IDENTITY, new byte[]{1, 2, 3});
+        DataElement trustedIdentity = new DataElement(TRUSTED_IDENTITY, new byte[]{1, 2, 3});
+        DataElement publicIdentity = new DataElement(PUBLIC_IDENTITY, new byte[]{1, 2, 3});
+        DataElement provisionedIdentity =
+                new DataElement(PROVISIONED_IDENTITY, new byte[]{1, 2, 3});
+
+        DataElement salt = new DataElement(SALT, new byte[]{1, 2, 3});
+        DataElement txPower = new DataElement(TX_POWER, new byte[]{1, 2, 3});
+
+        assertThat(privateIdentity.isIdentityDataType()).isTrue();
+        assertThat(trustedIdentity.isIdentityDataType()).isTrue();
+        assertThat(publicIdentity.isIdentityDataType()).isTrue();
+        assertThat(provisionedIdentity.isIdentityDataType()).isTrue();
+        assertThat(salt.isIdentityDataType()).isFalse();
+        assertThat(txPower.isIdentityDataType()).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_notEquals() {
+        DataElement dataElement = new DataElement(KEY, VALUE);
+        DataElement dataElement2 = new DataElement(KEY, new byte[]{1, 2, 1, 1});
+        DataElement dataElement3 = new DataElement(6, VALUE);
+
+        assertThat(dataElement.equals(dataElement2)).isFalse();
+        assertThat(dataElement.equals(dataElement3)).isFalse();
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairAntispoofKeyDeviceMetadataTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairAntispoofKeyDeviceMetadataTest.java
new file mode 100644
index 0000000..65c061b
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairAntispoofKeyDeviceMetadataTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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 android.nearby.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.FastPairAntispoofKeyDeviceMetadata;
+import android.nearby.FastPairDeviceMetadata;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairAntispoofKeyDeviceMetadataTest {
+
+    private static final String ASSISTANT_SETUP_HALFSHEET = "ASSISTANT_SETUP_HALFSHEET";
+    private static final String ASSISTANT_SETUP_NOTIFICATION = "ASSISTANT_SETUP_NOTIFICATION";
+    private static final int BLE_TX_POWER  = 5;
+    private static final String CONFIRM_PIN_DESCRIPTION = "CONFIRM_PIN_DESCRIPTION";
+    private static final String CONFIRM_PIN_TITLE = "CONFIRM_PIN_TITLE";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+    private static final float DELTA = 0.001f;
+    private static final int DEVICE_TYPE = 7;
+    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+    private static final String FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
+            "FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION";
+    private static final byte[] IMAGE = new byte[] {7, 9};
+    private static final String IMAGE_URL = "IMAGE_URL";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+            "INITIAL_NOTIFICATION_DESCRIPTION";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+    private static final String INTENT_URI = "INTENT_URI";
+    private static final String LOCALE = "LOCALE";
+    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+    private static final String SYNC_CONTACT_DESCRPTION = "SYNC_CONTACT_DESCRPTION";
+    private static final String SYNC_CONTACTS_TITLE = "SYNC_CONTACTS_TITLE";
+    private static final String SYNC_SMS_DESCRIPTION = "SYNC_SMS_DESCRIPTION";
+    private static final String SYNC_SMS_TITLE = "SYNC_SMS_TITLE";
+    private static final float TRIGGER_DISTANCE = 111;
+    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+            "UPDATE_COMPANION_APP_DESCRIPTION";
+    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+    private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+    private static final String NAME = "NAME";
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairAntispoofKeyDeviceMetadataNotNull() {
+        FastPairDeviceMetadata fastPairDeviceMetadata = genFastPairDeviceMetadata();
+        FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+                genFastPairAntispoofKeyDeviceMetadata(ANTI_SPOOFING_KEY, fastPairDeviceMetadata);
+
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+                ANTI_SPOOFING_KEY);
+        ensureFastPairDeviceMetadataAsExpected(
+                fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairAntispoofKeyDeviceMetadataNull() {
+        FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+                genFastPairAntispoofKeyDeviceMetadata(null, null);
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+                null);
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata()).isEqualTo(
+                null);
+    }
+
+    /* Verifies DeviceMetadata. */
+    private static void ensureFastPairDeviceMetadataAsExpected(FastPairDeviceMetadata metadata) {
+        assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+        assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(metadata.getDownloadCompanionAppDescription())
+                .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getFailConnectGoToSettingsDescription())
+                .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        assertThat(metadata.getImage()).isEqualTo(IMAGE);
+        assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+        assertThat(metadata.getInitialNotificationDescription())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+        assertThat(metadata.getName()).isEqualTo(NAME);
+        assertThat(metadata.getOpenCompanionAppDescription())
+                .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getRetroactivePairingDescription())
+                .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        assertThat(metadata.getSubsequentPairingDescription())
+                .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+        assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadata.getTrueWirelessImageUrlRightBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        assertThat(metadata.getUnableToConnectDescription())
+                .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadata.getUpdateCompanionAppDescription())
+                .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getWaitLaunchCompanionAppDescription())
+                .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Generates FastPairAntispoofKeyDeviceMetadata. */
+    private static FastPairAntispoofKeyDeviceMetadata genFastPairAntispoofKeyDeviceMetadata(
+            byte[] antispoofPublicKey, FastPairDeviceMetadata deviceMetadata) {
+        FastPairAntispoofKeyDeviceMetadata.Builder builder =
+                new FastPairAntispoofKeyDeviceMetadata.Builder();
+        builder.setAntispoofPublicKey(antispoofPublicKey);
+        builder.setFastPairDeviceMetadata(deviceMetadata);
+
+        return builder.build();
+    }
+
+    /* Generates FastPairDeviceMetadata. */
+    private static FastPairDeviceMetadata genFastPairDeviceMetadata() {
+        FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+        builder.setBleTxPower(BLE_TX_POWER);
+        builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        builder.setConnectSuccessCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        builder.setDeviceType(DEVICE_TYPE);
+        builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        builder.setImage(IMAGE);
+        builder.setImageUrl(IMAGE_URL);
+        builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+        builder.setInitialNotificationDescriptionNoAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+        builder.setIntentUri(INTENT_URI);
+        builder.setName(NAME);
+        builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+        builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+        builder.setTriggerDistance(TRIGGER_DISTANCE);
+        builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+        builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+        builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+        builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+        builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+        return builder.build();
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderServiceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderServiceTest.java
new file mode 100644
index 0000000..160da56
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderServiceTest.java
@@ -0,0 +1,994 @@
+/*
+ * 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 android.nearby.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.accounts.Account;
+import android.content.Intent;
+import android.nearby.FastPairAccountKeyDeviceMetadata;
+import android.nearby.FastPairAntispoofKeyDeviceMetadata;
+import android.nearby.FastPairDataProviderService;
+import android.nearby.FastPairDeviceMetadata;
+import android.nearby.FastPairDiscoveryItem;
+import android.nearby.FastPairEligibleAccount;
+import android.nearby.aidl.ByteArrayParcel;
+import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
+import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairDiscoveryItemParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
+import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
+import android.nearby.aidl.FastPairManageAccountRequestParcel;
+import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
+import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
+import android.nearby.aidl.IFastPairDataProvider;
+import android.nearby.aidl.IFastPairEligibleAccountsCallback;
+import android.nearby.aidl.IFastPairManageAccountCallback;
+import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairDataProviderServiceTest {
+
+    private static final String TAG = "FastPairDataProviderServiceTest";
+
+    private static final String ASSISTANT_SETUP_HALFSHEET = "ASSISTANT_SETUP_HALFSHEET";
+    private static final String ASSISTANT_SETUP_NOTIFICATION = "ASSISTANT_SETUP_NOTIFICATION";
+    private static final int BLE_TX_POWER  = 5;
+    private static final String CONFIRM_PIN_DESCRIPTION = "CONFIRM_PIN_DESCRIPTION";
+    private static final String CONFIRM_PIN_TITLE = "CONFIRM_PIN_TITLE";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+    private static final float DELTA = 0.001f;
+    private static final int DEVICE_TYPE = 7;
+    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+    private static final Account ELIGIBLE_ACCOUNT_1 = new Account("abc@google.com", "type1");
+    private static final boolean ELIGIBLE_ACCOUNT_1_OPT_IN = true;
+    private static final Account ELIGIBLE_ACCOUNT_2 = new Account("def@gmail.com", "type2");
+    private static final boolean ELIGIBLE_ACCOUNT_2_OPT_IN = false;
+    private static final Account MANAGE_ACCOUNT = new Account("ghi@gmail.com", "type3");
+    private static final Account ACCOUNTDEVICES_METADATA_ACCOUNT =
+            new Account("jk@gmail.com", "type4");
+    private static final int NUM_ACCOUNT_DEVICES = 2;
+
+    private static final int ERROR_CODE_BAD_REQUEST =
+            FastPairDataProviderService.ERROR_CODE_BAD_REQUEST;
+    private static final int MANAGE_ACCOUNT_REQUEST_TYPE =
+            FastPairDataProviderService.MANAGE_REQUEST_ADD;
+    private static final String ERROR_STRING = "ERROR_STRING";
+    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+    private static final String FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
+            "FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION";
+    private static final byte[] IMAGE = new byte[] {7, 9};
+    private static final String IMAGE_URL = "IMAGE_URL";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+            "INITIAL_NOTIFICATION_DESCRIPTION";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+    private static final String INTENT_URI = "INTENT_URI";
+    private static final String LOCALE = "LOCALE";
+    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+    private static final String SYNC_CONTACT_DESCRPTION = "SYNC_CONTACT_DESCRPTION";
+    private static final String SYNC_CONTACTS_TITLE = "SYNC_CONTACTS_TITLE";
+    private static final String SYNC_SMS_DESCRIPTION = "SYNC_SMS_DESCRIPTION";
+    private static final String SYNC_SMS_TITLE = "SYNC_SMS_TITLE";
+    private static final float TRIGGER_DISTANCE = 111;
+    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+            "UPDATE_COMPANION_APP_DESCRIPTION";
+    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+    private static final byte[] ACCOUNT_KEY = new byte[] {3};
+    private static final byte[] ACCOUNT_KEY_2 = new byte[] {9, 3};
+    private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[] {2, 8};
+    private static final byte[] REQUEST_MODEL_ID = new byte[] {1, 2, 3};
+    private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+    private static final String ACTION_URL = "ACTION_URL";
+    private static final int ACTION_URL_TYPE = 5;
+    private static final String APP_NAME = "APP_NAME";
+    private static final int ATTACHMENT_TYPE = 8;
+    private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[] {5, 7};
+    private static final byte[] BLE_RECORD_BYTES = new byte[]{2, 4};
+    private static final int DEBUG_CATEGORY = 9;
+    private static final String DEBUG_MESSAGE = "DEBUG_MESSAGE";
+    private static final String DESCRIPTION = "DESCRIPTION";
+    private static final String DEVICE_NAME = "DEVICE_NAME";
+    private static final String DISPLAY_URL = "DISPLAY_URL";
+    private static final String ENTITY_ID = "ENTITY_ID";
+    private static final String FEATURE_GRAPHIC_URL = "FEATURE_GRAPHIC_URL";
+    private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
+    private static final String GROUP_ID = "GROUP_ID";
+    private static final String  ICON_FIFE_URL = "ICON_FIFE_URL";
+    private static final byte[]  ICON_PNG = new byte[]{2, 5};
+    private static final String ID = "ID";
+    private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
+    private static final int LAST_USER_EXPERIENCE = 93;
+    private static final long LOST_MILLIS = 393284L;
+    private static final String MAC_ADDRESS = "MAC_ADDRESS";
+    private static final String NAME = "NAME";
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+    private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+    private static final int RSSI = 9;
+    private static final int STATE = 63;
+    private static final String TITLE = "TITLE";
+    private static final String TRIGGER_ID = "TRIGGER_ID";
+    private static final int TX_POWER = 62;
+    private static final int TYPE = 73;
+    private static final String BLE_ADDRESS = "BLE_ADDRESS";
+
+    private static final int ELIGIBLE_ACCOUNTS_NUM = 2;
+    private static final ImmutableList<FastPairEligibleAccount> ELIGIBLE_ACCOUNTS =
+            ImmutableList.of(
+                    genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_1,
+                            ELIGIBLE_ACCOUNT_1_OPT_IN),
+                    genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_2,
+                            ELIGIBLE_ACCOUNT_2_OPT_IN));
+    private static final int ACCOUNTKEY_DEVICE_NUM = 2;
+    private static final ImmutableList<FastPairAccountKeyDeviceMetadata>
+            FAST_PAIR_ACCOUNT_DEVICES_METADATA =
+            ImmutableList.of(
+                    genHappyPathFastPairAccountkeyDeviceMetadata(),
+                    genHappyPathFastPairAccountkeyDeviceMetadata());
+
+    private static final FastPairAntispoofKeyDeviceMetadataRequestParcel
+            FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL =
+            genFastPairAntispoofKeyDeviceMetadataRequestParcel();
+    private static final FastPairAccountDevicesMetadataRequestParcel
+            FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL =
+            genFastPairAccountDevicesMetadataRequestParcel();
+    private static final FastPairEligibleAccountsRequestParcel
+            FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL =
+            genFastPairEligibleAccountsRequestParcel();
+    private static final FastPairManageAccountRequestParcel
+            FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL =
+            genFastPairManageAccountRequestParcel();
+    private static final FastPairManageAccountDeviceRequestParcel
+            FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL =
+            genFastPairManageAccountDeviceRequestParcel();
+    private static final FastPairAntispoofKeyDeviceMetadata
+            HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA =
+            genHappyPathFastPairAntispoofKeyDeviceMetadata();
+
+    @Captor private ArgumentCaptor<FastPairEligibleAccountParcel[]>
+            mFastPairEligibleAccountParcelsArgumentCaptor;
+    @Captor private ArgumentCaptor<FastPairAccountKeyDeviceMetadataParcel[]>
+            mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor;
+
+    @Mock private FastPairDataProviderService mMockFastPairDataProviderService;
+    @Mock private IFastPairAntispoofKeyDeviceMetadataCallback.Stub
+            mAntispoofKeyDeviceMetadataCallback;
+    @Mock private IFastPairAccountDevicesMetadataCallback.Stub mAccountDevicesMetadataCallback;
+    @Mock private IFastPairEligibleAccountsCallback.Stub mEligibleAccountsCallback;
+    @Mock private IFastPairManageAccountCallback.Stub mManageAccountCallback;
+    @Mock private IFastPairManageAccountDeviceCallback.Stub mManageAccountDeviceCallback;
+
+    private MyHappyPathProvider mHappyPathFastPairDataProvider;
+    private MyErrorPathProvider mErrorPathFastPairDataProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+
+        mHappyPathFastPairDataProvider =
+                new MyHappyPathProvider(TAG, mMockFastPairDataProviderService);
+        mErrorPathFastPairDataProvider =
+                new MyErrorPathProvider(TAG, mMockFastPairDataProviderService);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+                FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+                mAntispoofKeyDeviceMetadataCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest>
+                fastPairAntispoofKeyDeviceMetadataRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class
+                );
+        verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+                fastPairAntispoofKeyDeviceMetadataRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+        ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        final ArgumentCaptor<FastPairAntispoofKeyDeviceMetadataParcel>
+                fastPairAntispoofKeyDeviceMetadataParcelCaptor =
+                ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataParcel.class);
+        verify(mAntispoofKeyDeviceMetadataCallback).onFastPairAntispoofKeyDeviceMetadataReceived(
+                fastPairAntispoofKeyDeviceMetadataParcelCaptor.capture());
+        ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataParcelCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairAccountDevicesMetadata() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+                FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+                mAccountDevicesMetadataCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairAccountDevicesMetadataRequest>
+                fastPairAccountDevicesMetadataRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+                fastPairAccountDevicesMetadataRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+        ensureHappyPathAsExpected(fastPairAccountDevicesMetadataRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        verify(mAccountDevicesMetadataCallback).onFastPairAccountDevicesMetadataReceived(
+                mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.capture());
+        ensureHappyPathAsExpected(
+                mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairEligibleAccounts() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+                FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+                mEligibleAccountsCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairEligibleAccountsRequest>
+                fastPairEligibleAccountsRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairEligibleAccountsRequest.class);
+        verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+                fastPairEligibleAccountsRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+        ensureHappyPathAsExpected(fastPairEligibleAccountsRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        verify(mEligibleAccountsCallback).onFastPairEligibleAccountsReceived(
+                mFastPairEligibleAccountParcelsArgumentCaptor.capture());
+        ensureHappyPathAsExpected(mFastPairEligibleAccountParcelsArgumentCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathManageFastPairAccount() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().manageFastPairAccount(
+                FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+                mManageAccountCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountRequest>
+                fastPairManageAccountRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairManageAccountRequest.class);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+                fastPairManageAccountRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        ensureHappyPathAsExpected(fastPairManageAccountRequestCaptor.getValue());
+
+        // AOSP receives SUCCESS response.
+        verify(mManageAccountCallback).onSuccess();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathManageFastPairAccountDevice() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+                FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+                mManageAccountDeviceCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountDeviceRequest>
+                fastPairManageAccountDeviceRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairManageAccountDeviceRequest.class);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+                fastPairManageAccountDeviceRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        ensureHappyPathAsExpected(fastPairManageAccountDeviceRequestCaptor.getValue());
+
+        // AOSP receives SUCCESS response.
+        verify(mManageAccountDeviceCallback).onSuccess();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+                FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+                mAntispoofKeyDeviceMetadataCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class),
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+        verify(mAntispoofKeyDeviceMetadataCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairAccountDevicesMetadata() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+                FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+                mAccountDevicesMetadataCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class),
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+        verify(mAccountDevicesMetadataCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairEligibleAccounts() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+                FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+                mEligibleAccountsCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+                any(FastPairDataProviderService.FastPairEligibleAccountsRequest.class),
+                any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+        verify(mEligibleAccountsCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathManageFastPairAccount() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().manageFastPairAccount(
+                FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+                mManageAccountCallback);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+                any(FastPairDataProviderService.FastPairManageAccountRequest.class),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        verify(mManageAccountCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathManageFastPairAccountDevice() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+                FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+                mManageAccountDeviceCallback);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+                any(FastPairDataProviderService.FastPairManageAccountDeviceRequest.class),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        verify(mManageAccountDeviceCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    public static class MyHappyPathProvider extends FastPairDataProviderService {
+
+        private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+        public MyHappyPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+            super(tag);
+            mMockFastPairDataProviderService = mock;
+        }
+
+        public IFastPairDataProvider asProvider() {
+            Intent intent = new Intent();
+            return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+        }
+
+        @Override
+        public void onLoadFastPairAntispoofKeyDeviceMetadata(
+                @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+                @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+                    request, callback);
+            callback.onFastPairAntispoofKeyDeviceMetadataReceived(
+                    HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA);
+        }
+
+        @Override
+        public void onLoadFastPairAccountDevicesMetadata(
+                @NonNull FastPairAccountDevicesMetadataRequest request,
+                @NonNull FastPairAccountDevicesMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+                    request, callback);
+            callback.onFastPairAccountDevicesMetadataReceived(FAST_PAIR_ACCOUNT_DEVICES_METADATA);
+        }
+
+        @Override
+        public void onLoadFastPairEligibleAccounts(
+                @NonNull FastPairEligibleAccountsRequest request,
+                @NonNull FastPairEligibleAccountsCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(
+                    request, callback);
+            callback.onFastPairEligibleAccountsReceived(ELIGIBLE_ACCOUNTS);
+        }
+
+        @Override
+        public void onManageFastPairAccount(
+                @NonNull FastPairManageAccountRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+            callback.onSuccess();
+        }
+
+        @Override
+        public void onManageFastPairAccountDevice(
+                @NonNull FastPairManageAccountDeviceRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+            callback.onSuccess();
+        }
+    }
+
+    public static class MyErrorPathProvider extends FastPairDataProviderService {
+
+        private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+        public MyErrorPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+            super(tag);
+            mMockFastPairDataProviderService = mock;
+        }
+
+        public IFastPairDataProvider asProvider() {
+            Intent intent = new Intent();
+            return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+        }
+
+        @Override
+        public void onLoadFastPairAntispoofKeyDeviceMetadata(
+                @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+                @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+                    request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onLoadFastPairAccountDevicesMetadata(
+                @NonNull FastPairAccountDevicesMetadataRequest request,
+                @NonNull FastPairAccountDevicesMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+                    request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onLoadFastPairEligibleAccounts(
+                @NonNull FastPairEligibleAccountsRequest request,
+                @NonNull FastPairEligibleAccountsCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onManageFastPairAccount(
+                @NonNull FastPairManageAccountRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onManageFastPairAccountDevice(
+                @NonNull FastPairManageAccountDeviceRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+    }
+
+    /* Generates AntispoofKeyDeviceMetadataRequestParcel. */
+    private static FastPairAntispoofKeyDeviceMetadataRequestParcel
+            genFastPairAntispoofKeyDeviceMetadataRequestParcel() {
+        FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel =
+                new FastPairAntispoofKeyDeviceMetadataRequestParcel();
+        requestParcel.modelId = REQUEST_MODEL_ID;
+
+        return requestParcel;
+    }
+
+    /* Generates AccountDevicesMetadataRequestParcel. */
+    private static FastPairAccountDevicesMetadataRequestParcel
+            genFastPairAccountDevicesMetadataRequestParcel() {
+        FastPairAccountDevicesMetadataRequestParcel requestParcel =
+                new FastPairAccountDevicesMetadataRequestParcel();
+
+        requestParcel.account = ACCOUNTDEVICES_METADATA_ACCOUNT;
+        requestParcel.deviceAccountKeys = new ByteArrayParcel[NUM_ACCOUNT_DEVICES];
+        requestParcel.deviceAccountKeys[0] = new ByteArrayParcel();
+        requestParcel.deviceAccountKeys[1] = new ByteArrayParcel();
+        requestParcel.deviceAccountKeys[0].byteArray = ACCOUNT_KEY;
+        requestParcel.deviceAccountKeys[1].byteArray = ACCOUNT_KEY_2;
+
+        return requestParcel;
+    }
+
+    /* Generates FastPairEligibleAccountsRequestParcel. */
+    private static FastPairEligibleAccountsRequestParcel
+            genFastPairEligibleAccountsRequestParcel() {
+        FastPairEligibleAccountsRequestParcel requestParcel =
+                new FastPairEligibleAccountsRequestParcel();
+        // No fields since FastPairEligibleAccountsRequestParcel is just a place holder now.
+        return requestParcel;
+    }
+
+    /* Generates FastPairManageAccountRequestParcel. */
+    private static FastPairManageAccountRequestParcel
+            genFastPairManageAccountRequestParcel() {
+        FastPairManageAccountRequestParcel requestParcel =
+                new FastPairManageAccountRequestParcel();
+        requestParcel.account = MANAGE_ACCOUNT;
+        requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+
+        return requestParcel;
+    }
+
+    /* Generates FastPairManageAccountDeviceRequestParcel. */
+    private static FastPairManageAccountDeviceRequestParcel
+            genFastPairManageAccountDeviceRequestParcel() {
+        FastPairManageAccountDeviceRequestParcel requestParcel =
+                new FastPairManageAccountDeviceRequestParcel();
+        requestParcel.account = MANAGE_ACCOUNT;
+        requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+        requestParcel.accountKeyDeviceMetadata =
+                genHappyPathFastPairAccountkeyDeviceMetadataParcel();
+
+        return requestParcel;
+    }
+
+    /* Generates Happy Path AntispoofKeyDeviceMetadata. */
+    private static FastPairAntispoofKeyDeviceMetadata
+            genHappyPathFastPairAntispoofKeyDeviceMetadata() {
+        FastPairAntispoofKeyDeviceMetadata.Builder builder =
+                new FastPairAntispoofKeyDeviceMetadata.Builder();
+        builder.setAntispoofPublicKey(ANTI_SPOOFING_KEY);
+        builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path FastPairAccountKeyDeviceMetadata. */
+    private static FastPairAccountKeyDeviceMetadata
+            genHappyPathFastPairAccountkeyDeviceMetadata() {
+        FastPairAccountKeyDeviceMetadata.Builder builder =
+                new FastPairAccountKeyDeviceMetadata.Builder();
+        builder.setDeviceAccountKey(ACCOUNT_KEY);
+        builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+        builder.setSha256DeviceAccountKeyPublicAddress(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        builder.setFastPairDiscoveryItem(genHappyPathFastPairDiscoveryItem());
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+    private static FastPairAccountKeyDeviceMetadataParcel
+            genHappyPathFastPairAccountkeyDeviceMetadataParcel() {
+        FastPairAccountKeyDeviceMetadataParcel parcel =
+                new FastPairAccountKeyDeviceMetadataParcel();
+        parcel.deviceAccountKey = ACCOUNT_KEY;
+        parcel.metadata = genHappyPathFastPairDeviceMetadataParcel();
+        parcel.sha256DeviceAccountKeyPublicAddress = SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS;
+        parcel.discoveryItem = genHappyPathFastPairDiscoveryItemParcel();
+
+        return parcel;
+    }
+
+    /* Generates Happy Path DiscoveryItem. */
+    private static FastPairDiscoveryItem genHappyPathFastPairDiscoveryItem() {
+        FastPairDiscoveryItem.Builder builder = new FastPairDiscoveryItem.Builder();
+
+        builder.setActionUrl(ACTION_URL);
+        builder.setActionUrlType(ACTION_URL_TYPE);
+        builder.setAppName(APP_NAME);
+        builder.setAuthenticationPublicKeySecp256r1(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        builder.setDescription(DESCRIPTION);
+        builder.setDeviceName(DEVICE_NAME);
+        builder.setDisplayUrl(DISPLAY_URL);
+        builder.setFirstObservationTimestampMillis(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        builder.setIconFfeUrl(ICON_FIFE_URL);
+        builder.setIconPng(ICON_PNG);
+        builder.setId(ID);
+        builder.setLastObservationTimestampMillis(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        builder.setMacAddress(MAC_ADDRESS);
+        builder.setPackageName(PACKAGE_NAME);
+        builder.setPendingAppInstallTimestampMillis(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        builder.setRssi(RSSI);
+        builder.setState(STATE);
+        builder.setTitle(TITLE);
+        builder.setTriggerId(TRIGGER_ID);
+        builder.setTxPower(TX_POWER);
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path DiscoveryItemParcel. */
+    private static FastPairDiscoveryItemParcel genHappyPathFastPairDiscoveryItemParcel() {
+        FastPairDiscoveryItemParcel parcel = new FastPairDiscoveryItemParcel();
+
+        parcel.actionUrl = ACTION_URL;
+        parcel.actionUrlType = ACTION_URL_TYPE;
+        parcel.appName = APP_NAME;
+        parcel.authenticationPublicKeySecp256r1 = AUTHENTICATION_PUBLIC_KEY_SEC_P256R1;
+        parcel.description = DESCRIPTION;
+        parcel.deviceName = DEVICE_NAME;
+        parcel.displayUrl = DISPLAY_URL;
+        parcel.firstObservationTimestampMillis = FIRST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.iconFifeUrl = ICON_FIFE_URL;
+        parcel.iconPng = ICON_PNG;
+        parcel.id = ID;
+        parcel.lastObservationTimestampMillis = LAST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.macAddress = MAC_ADDRESS;
+        parcel.packageName = PACKAGE_NAME;
+        parcel.pendingAppInstallTimestampMillis = PENDING_APP_INSTALL_TIMESTAMP_MILLIS;
+        parcel.rssi = RSSI;
+        parcel.state = STATE;
+        parcel.title = TITLE;
+        parcel.triggerId = TRIGGER_ID;
+        parcel.txPower = TX_POWER;
+
+        return parcel;
+    }
+
+    /* Generates Happy Path DeviceMetadata. */
+    private static FastPairDeviceMetadata genHappyPathFastPairDeviceMetadata() {
+        FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+        builder.setBleTxPower(BLE_TX_POWER);
+        builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        builder.setConnectSuccessCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        builder.setDeviceType(DEVICE_TYPE);
+        builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        builder.setImage(IMAGE);
+        builder.setImageUrl(IMAGE_URL);
+        builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+        builder.setInitialNotificationDescriptionNoAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+        builder.setIntentUri(INTENT_URI);
+        builder.setName(NAME);
+        builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+        builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+        builder.setTriggerDistance(TRIGGER_DISTANCE);
+        builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+        builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+        builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+        builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+        builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path DeviceMetadataParcel. */
+    private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+        FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+        parcel.bleTxPower = BLE_TX_POWER;
+        parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
+        parcel.connectSuccessCompanionAppNotInstalled =
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
+        parcel.deviceType = DEVICE_TYPE;
+        parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
+        parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
+        parcel.image = IMAGE;
+        parcel.imageUrl = IMAGE_URL;
+        parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
+        parcel.initialNotificationDescriptionNoAccount =
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
+        parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
+        parcel.intentUri = INTENT_URI;
+        parcel.name = NAME;
+        parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+        parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+        parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+        parcel.triggerDistance = TRIGGER_DISTANCE;
+        parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
+        parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
+        parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
+        parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
+        parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
+        parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
+        parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
+
+        return parcel;
+    }
+
+    /* Generates Happy Path FastPairEligibleAccount. */
+    private static FastPairEligibleAccount genHappyPathFastPairEligibleAccount(
+            Account account, boolean optIn) {
+        FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+        builder.setAccount(account);
+        builder.setOptIn(optIn);
+
+        return builder.build();
+    }
+
+    /* Verifies Happy Path AntispoofKeyDeviceMetadataRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest request) {
+        assertThat(request.getModelId()).isEqualTo(REQUEST_MODEL_ID);
+    }
+
+    /* Verifies Happy Path AccountDevicesMetadataRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairAccountDevicesMetadataRequest request) {
+        assertThat(request.getAccount()).isEqualTo(ACCOUNTDEVICES_METADATA_ACCOUNT);
+        assertThat(request.getDeviceAccountKeys().size()).isEqualTo(ACCOUNTKEY_DEVICE_NUM);
+        assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY);
+        assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY_2);
+    }
+
+    /* Verifies Happy Path FastPairEligibleAccountsRequest. */
+    @SuppressWarnings("UnusedVariable")
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairEligibleAccountsRequest request) {
+        // No fields since FastPairEligibleAccountsRequest is just a place holder now.
+    }
+
+    /* Verifies Happy Path FastPairManageAccountRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairManageAccountRequest request) {
+        assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+        assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+    }
+
+    /* Verifies Happy Path FastPairManageAccountDeviceRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairManageAccountDeviceRequest request) {
+        assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+        assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+        ensureHappyPathAsExpected(request.getAccountKeyDeviceMetadata());
+    }
+
+    /* Verifies Happy Path AntispoofKeyDeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.antispoofPublicKey).isEqualTo(ANTI_SPOOFING_KEY);
+        ensureHappyPathAsExpected(metadataParcel.deviceMetadata);
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel[]. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
+        assertThat(metadataParcels).isNotNull();
+        assertThat(metadataParcels).hasLength(ACCOUNTKEY_DEVICE_NUM);
+        for (FastPairAccountKeyDeviceMetadataParcel parcel: metadataParcels) {
+            ensureHappyPathAsExpected(parcel);
+        }
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.deviceAccountKey).isEqualTo(ACCOUNT_KEY);
+        assertThat(metadataParcel.sha256DeviceAccountKeyPublicAddress)
+                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        ensureHappyPathAsExpected(metadataParcel.metadata);
+        ensureHappyPathAsExpected(metadataParcel.discoveryItem);
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadata. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadata metadata) {
+        assertThat(metadata.getDeviceAccountKey()).isEqualTo(ACCOUNT_KEY);
+        assertThat(metadata.getSha256DeviceAccountKeyPublicAddress())
+                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        ensureHappyPathAsExpected(metadata.getFastPairDeviceMetadata());
+        ensureHappyPathAsExpected(metadata.getFastPairDiscoveryItem());
+    }
+
+    /* Verifies Happy Path DeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(FastPairDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.bleTxPower).isEqualTo(BLE_TX_POWER);
+
+        assertThat(metadataParcel.connectSuccessCompanionAppInstalled).isEqualTo(
+                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadataParcel.connectSuccessCompanionAppNotInstalled).isEqualTo(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+
+        assertThat(metadataParcel.deviceType).isEqualTo(DEVICE_TYPE);
+        assertThat(metadataParcel.downloadCompanionAppDescription).isEqualTo(
+                DOWNLOAD_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.failConnectGoToSettingsDescription).isEqualTo(
+                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+
+        assertThat(metadataParcel.image).isEqualTo(IMAGE);
+        assertThat(metadataParcel.imageUrl).isEqualTo(IMAGE_URL);
+        assertThat(metadataParcel.initialNotificationDescription).isEqualTo(
+                INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadataParcel.initialNotificationDescriptionNoAccount).isEqualTo(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadataParcel.initialPairingDescription).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
+
+        assertThat(metadataParcel.name).isEqualTo(NAME);
+
+        assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
+                OPEN_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.retroactivePairingDescription).isEqualTo(
+                RETRO_ACTIVE_PAIRING_DESCRIPTION);
+
+        assertThat(metadataParcel.subsequentPairingDescription).isEqualTo(
+                SUBSEQUENT_PAIRING_DESCRIPTION);
+
+        assertThat(metadataParcel.triggerDistance).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadataParcel.trueWirelessImageUrlCase).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadataParcel.trueWirelessImageUrlLeftBud).isEqualTo(
+                TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadataParcel.trueWirelessImageUrlRightBud).isEqualTo(
+                TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+
+        assertThat(metadataParcel.unableToConnectDescription).isEqualTo(
+                UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadataParcel.unableToConnectTitle).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadataParcel.updateCompanionAppDescription).isEqualTo(
+                UPDATE_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.waitLaunchCompanionAppDescription).isEqualTo(
+                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Verifies Happy Path DeviceMetadata. */
+    private static void ensureHappyPathAsExpected(FastPairDeviceMetadata metadata) {
+        assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+        assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(metadata.getDownloadCompanionAppDescription())
+                .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getFailConnectGoToSettingsDescription())
+                .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        assertThat(metadata.getImage()).isEqualTo(IMAGE);
+        assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+        assertThat(metadata.getInitialNotificationDescription())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+        assertThat(metadata.getName()).isEqualTo(NAME);
+        assertThat(metadata.getOpenCompanionAppDescription())
+                .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getRetroactivePairingDescription())
+                .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        assertThat(metadata.getSubsequentPairingDescription())
+                .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+        assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadata.getTrueWirelessImageUrlRightBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        assertThat(metadata.getUnableToConnectDescription())
+                .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadata.getUpdateCompanionAppDescription())
+                .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getWaitLaunchCompanionAppDescription())
+                .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Verifies Happy Path FastPairDiscoveryItemParcel. */
+    private static void ensureHappyPathAsExpected(FastPairDiscoveryItemParcel itemParcel) {
+        assertThat(itemParcel.actionUrl).isEqualTo(ACTION_URL);
+        assertThat(itemParcel.actionUrlType).isEqualTo(ACTION_URL_TYPE);
+        assertThat(itemParcel.appName).isEqualTo(APP_NAME);
+        assertThat(itemParcel.authenticationPublicKeySecp256r1)
+                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        assertThat(itemParcel.description).isEqualTo(DESCRIPTION);
+        assertThat(itemParcel.deviceName).isEqualTo(DEVICE_NAME);
+        assertThat(itemParcel.displayUrl).isEqualTo(DISPLAY_URL);
+        assertThat(itemParcel.firstObservationTimestampMillis)
+                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.iconFifeUrl).isEqualTo(ICON_FIFE_URL);
+        assertThat(itemParcel.iconPng).isEqualTo(ICON_PNG);
+        assertThat(itemParcel.id).isEqualTo(ID);
+        assertThat(itemParcel.lastObservationTimestampMillis)
+                .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.macAddress).isEqualTo(MAC_ADDRESS);
+        assertThat(itemParcel.packageName).isEqualTo(PACKAGE_NAME);
+        assertThat(itemParcel.pendingAppInstallTimestampMillis)
+                .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.rssi).isEqualTo(RSSI);
+        assertThat(itemParcel.state).isEqualTo(STATE);
+        assertThat(itemParcel.title).isEqualTo(TITLE);
+        assertThat(itemParcel.triggerId).isEqualTo(TRIGGER_ID);
+        assertThat(itemParcel.txPower).isEqualTo(TX_POWER);
+    }
+
+    /* Verifies Happy Path FastPairDiscoveryItem. */
+    private static void ensureHappyPathAsExpected(FastPairDiscoveryItem item) {
+        assertThat(item.getActionUrl()).isEqualTo(ACTION_URL);
+        assertThat(item.getActionUrlType()).isEqualTo(ACTION_URL_TYPE);
+        assertThat(item.getAppName()).isEqualTo(APP_NAME);
+        assertThat(item.getAuthenticationPublicKeySecp256r1())
+                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        assertThat(item.getDescription()).isEqualTo(DESCRIPTION);
+        assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
+        assertThat(item.getDisplayUrl()).isEqualTo(DISPLAY_URL);
+        assertThat(item.getFirstObservationTimestampMillis())
+                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(item.getIconFfeUrl()).isEqualTo(ICON_FIFE_URL);
+        assertThat(item.getIconPng()).isEqualTo(ICON_PNG);
+        assertThat(item.getId()).isEqualTo(ID);
+        assertThat(item.getLastObservationTimestampMillis())
+                .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(item.getMacAddress()).isEqualTo(MAC_ADDRESS);
+        assertThat(item.getPackageName()).isEqualTo(PACKAGE_NAME);
+        assertThat(item.getPendingAppInstallTimestampMillis())
+                .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        assertThat(item.getRssi()).isEqualTo(RSSI);
+        assertThat(item.getState()).isEqualTo(STATE);
+        assertThat(item.getTitle()).isEqualTo(TITLE);
+        assertThat(item.getTriggerId()).isEqualTo(TRIGGER_ID);
+        assertThat(item.getTxPower()).isEqualTo(TX_POWER);
+    }
+
+    /* Verifies Happy Path EligibleAccountParcel[]. */
+    private static void ensureHappyPathAsExpected(FastPairEligibleAccountParcel[] accountsParcel) {
+        assertThat(accountsParcel).hasLength(ELIGIBLE_ACCOUNTS_NUM);
+
+        assertThat(accountsParcel[0].account).isEqualTo(ELIGIBLE_ACCOUNT_1);
+        assertThat(accountsParcel[0].optIn).isEqualTo(ELIGIBLE_ACCOUNT_1_OPT_IN);
+
+        assertThat(accountsParcel[1].account).isEqualTo(ELIGIBLE_ACCOUNT_2);
+        assertThat(accountsParcel[1].optIn).isEqualTo(ELIGIBLE_ACCOUNT_2_OPT_IN);
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairEligibleAccountTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairEligibleAccountTest.java
new file mode 100644
index 0000000..0d91d4e
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairEligibleAccountTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 android.nearby.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+import android.nearby.FastPairEligibleAccount;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairEligibleAccountTest {
+
+    private static final Account ACCOUNT = new Account("abc@google.com", "type1");
+    private static final Account ACCOUNT_NULL = null;
+
+    private static final boolean OPT_IN_TRUE = true;
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairEligibleAccountNotNull() {
+        FastPairEligibleAccount eligibleAccount =
+                genFastPairEligibleAccount(ACCOUNT, OPT_IN_TRUE);
+
+        assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT);
+        assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairEligibleAccountNull() {
+        FastPairEligibleAccount eligibleAccount =
+                genFastPairEligibleAccount(ACCOUNT_NULL, OPT_IN_TRUE);
+
+        assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT_NULL);
+        assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+    }
+
+    /* Generates FastPairEligibleAccount. */
+    private static FastPairEligibleAccount genFastPairEligibleAccount(
+            Account account, boolean optIn) {
+        FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+        builder.setAccount(account);
+        builder.setOptIn(optIn);
+
+        return builder.build();
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
index 654b852..2907e56 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
@@ -22,6 +22,7 @@
 
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PublicCredential;
 import android.os.Build;
 import android.os.Parcel;
 
@@ -41,8 +42,11 @@
 
     private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
     private static final byte[] SCAN_DATA = new byte[] {1, 2, 3, 4};
+    private static final byte[] SALT = new byte[] {1, 2, 3, 4};
     private static final String FAST_PAIR_MODEL_ID = "1234";
     private static final int RSSI = -60;
+    private static final int TX_POWER = -10;
+    private static final int ACTION = 1;
 
     private NearbyDeviceParcelable.Builder mBuilder;
 
@@ -61,20 +65,36 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 33, codeName = "T")
-    public void test_defaultNullFields() {
+    public void testNullFields() {
+        PublicCredential publicCredential =
+                new PublicCredential.Builder(
+                        new byte[] {1},
+                        new byte[] {2},
+                        new byte[] {3},
+                        new byte[] {4},
+                        new byte[] {5})
+                        .build();
         NearbyDeviceParcelable nearbyDeviceParcelable =
                 new NearbyDeviceParcelable.Builder()
                         .setMedium(NearbyDevice.Medium.BLE)
+                        .setPublicCredential(publicCredential)
+                        .setAction(ACTION)
                         .setRssi(RSSI)
+                        .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                        .setTxPower(TX_POWER)
+                        .setSalt(SALT)
                         .build();
 
         assertThat(nearbyDeviceParcelable.getName()).isNull();
         assertThat(nearbyDeviceParcelable.getFastPairModelId()).isNull();
         assertThat(nearbyDeviceParcelable.getBluetoothAddress()).isNull();
         assertThat(nearbyDeviceParcelable.getData()).isNull();
-
         assertThat(nearbyDeviceParcelable.getMedium()).isEqualTo(NearbyDevice.Medium.BLE);
         assertThat(nearbyDeviceParcelable.getRssi()).isEqualTo(RSSI);
+        assertThat(nearbyDeviceParcelable.getAction()).isEqualTo(ACTION);
+        assertThat(nearbyDeviceParcelable.getPublicCredential()).isEqualTo(publicCredential);
+        assertThat(nearbyDeviceParcelable.getSalt()).isEqualTo(SALT);
+        assertThat(nearbyDeviceParcelable.getTxPower()).isEqualTo(TX_POWER);
     }
 
     @Test
@@ -114,7 +134,6 @@
     @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testWriteParcel_nullBluetoothAddress() {
         NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setBluetoothAddress(null).build();
-
         Parcel parcel = Parcel.obtain();
         nearbyDeviceParcelable.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -124,4 +143,36 @@
 
         assertThat(actualNearbyDevice.getBluetoothAddress()).isNull();
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
+    public void describeContents() {
+        NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setBluetoothAddress(null).build();
+        assertThat(nearbyDeviceParcelable.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
+    public void testEqual() {
+        PublicCredential publicCredential =
+                new PublicCredential.Builder(
+                        new byte[] {1},
+                        new byte[] {2},
+                        new byte[] {3},
+                        new byte[] {4},
+                        new byte[] {5})
+                        .build();
+        NearbyDeviceParcelable nearbyDeviceParcelable1 =
+                mBuilder.setPublicCredential(publicCredential).build();
+        NearbyDeviceParcelable nearbyDeviceParcelable2 =
+                mBuilder.setPublicCredential(publicCredential).build();
+        assertThat(nearbyDeviceParcelable1.equals(nearbyDeviceParcelable2)).isTrue();
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        NearbyDeviceParcelable[] nearbyDeviceParcelables =
+                NearbyDeviceParcelable.CREATOR.newArray(2);
+        assertThat(nearbyDeviceParcelables.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
index f37800a..8ca5a94 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
@@ -16,6 +16,8 @@
 
 package android.nearby.cts;
 
+import static android.nearby.NearbyDevice.Medium.BLE;
+
 import android.annotation.TargetApi;
 import android.nearby.FastPairDevice;
 import android.nearby.NearbyDevice;
@@ -34,13 +36,18 @@
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 @TargetApi(Build.VERSION_CODES.TIRAMISU)
 public class NearbyDeviceTest {
+    private static final String NAME = "NearbyDevice";
+    private static final String MODEL_ID = "112233";
+    private static final int TX_POWER = -10;
+    private static final int RSSI = -60;
+    private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+    private static final byte[] SCAN_DATA = new byte[] {1, 2, 3, 4};
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void test_isValidMedium() {
         assertThat(NearbyDevice.isValidMedium(1)).isTrue();
         assertThat(NearbyDevice.isValidMedium(2)).isTrue();
-
         assertThat(NearbyDevice.isValidMedium(0)).isFalse();
         assertThat(NearbyDevice.isValidMedium(3)).isFalse();
     }
@@ -49,11 +56,55 @@
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void test_getMedium_fromChild() {
         FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .addMedium(NearbyDevice.Medium.BLE)
-                .setRssi(-60)
+                .addMedium(BLE)
+                .setRssi(RSSI)
                 .build();
 
         assertThat(fastPairDevice.getMediums()).contains(1);
-        assertThat(fastPairDevice.getRssi()).isEqualTo(-60);
+        assertThat(fastPairDevice.getRssi()).isEqualTo(RSSI);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEqual() {
+        FastPairDevice fastPairDevice1 = new FastPairDevice.Builder()
+                .setModelId(MODEL_ID)
+                .setTxPower(TX_POWER)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setData(SCAN_DATA)
+                .setRssi(RSSI)
+                .addMedium(BLE)
+                .setName(NAME)
+                .build();
+        FastPairDevice fastPairDevice2 = new FastPairDevice.Builder()
+                .setModelId(MODEL_ID)
+                .setTxPower(TX_POWER)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setData(SCAN_DATA)
+                .setRssi(RSSI)
+                .addMedium(BLE)
+                .setName(NAME)
+                .build();
+
+        assertThat(fastPairDevice1.equals(fastPairDevice1)).isTrue();
+        assertThat(fastPairDevice1.equals(fastPairDevice2)).isTrue();
+        assertThat(fastPairDevice1.equals(null)).isFalse();
+        assertThat(fastPairDevice1.hashCode()).isEqualTo(fastPairDevice2.hashCode());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testToString() {
+        FastPairDevice fastPairDevice1 = new FastPairDevice.Builder()
+                .addMedium(BLE)
+                .setRssi(RSSI)
+                .setModelId(MODEL_ID)
+                .setTxPower(TX_POWER)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .build();
+
+        assertThat(fastPairDevice1.toString())
+                .isEqualTo("FastPairDevice [medium={BLE} rssi=-60 "
+                        + "txPower=-10 modelId=112233 bluetoothAddress=00:11:22:33:FF:EE]");
     }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 7696a61..f733266 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -57,6 +57,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * TODO(b/215435939) This class doesn't include any logic yet. Because SELinux denies access to
@@ -94,7 +95,12 @@
         @Override
         public void onLost(@NonNull NearbyDevice device) {
         }
+
+        @Override
+        public void onError(int errorCode) {
+        }
     };
+
     private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
 
     @Before
@@ -158,6 +164,22 @@
         mNearbyManager.stopBroadcast(callback);
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void setFastPairScanEnabled() {
+        mNearbyManager.setFastPairScanEnabled(mContext, true);
+        assertThat(mNearbyManager.isFastPairScanEnabled(mContext)).isTrue();
+        mNearbyManager.setFastPairScanEnabled(mContext, false);
+        assertThat(mNearbyManager.isFastPairScanEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 34, codeName = "U")
+    public void queryOffloadScanSupport() {
+        OffloadCallback callback = new OffloadCallback();
+        mNearbyManager.queryOffloadScanSupport(EXECUTOR, callback);
+    }
+
     private void enableBluetooth() {
         BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
         BluetoothAdapter bluetoothAdapter = manager.getAdapter();
@@ -165,4 +187,11 @@
             assertThat(BTAdapterUtils.enableAdapter(bluetoothAdapter, mContext)).isTrue();
         }
     }
+
+    private class OffloadCallback implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean aBoolean) {
+            // no-op for now
+        }
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
index eaa5ca1..71be889 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
@@ -114,4 +114,18 @@
         assertThat(parcelRequest.getType()).isEqualTo(BROADCAST_TYPE_NEARBY_PRESENCE);
 
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        PresenceBroadcastRequest broadcastRequest = mBuilder.build();
+        assertThat(broadcastRequest.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        PresenceBroadcastRequest[] presenceBroadcastRequests =
+                PresenceBroadcastRequest.CREATOR.newArray(2);
+        assertThat(presenceBroadcastRequests.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
index 94f8fe7..ea1de6b 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
@@ -104,4 +104,24 @@
         assertThat(parcelDevice.getMediums()).containsExactly(MEDIUM);
         assertThat(parcelDevice.getName()).isEqualTo(DEVICE_NAME);
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        PresenceDevice device =
+                new PresenceDevice.Builder(DEVICE_ID, SALT, SECRET_ID, ENCRYPTED_IDENTITY)
+                        .addExtendedProperty(new DataElement(KEY, VALUE))
+                        .setRssi(RSSI)
+                        .addMedium(MEDIUM)
+                        .setName(DEVICE_NAME)
+                        .build();
+        assertThat(device.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        PresenceDevice[] devices =
+                PresenceDevice.CREATOR.newArray(2);
+        assertThat(devices.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
index cecdfd2..77e7dc5 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
@@ -90,5 +90,21 @@
         assertThat(parcelFilter.getType()).isEqualTo(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE);
         assertThat(parcelFilter.getMaxPathLoss()).isEqualTo(RSSI);
         assertThat(parcelFilter.getPresenceActions()).containsExactly(ACTION);
+        assertThat(parcelFilter.getExtendedProperties().get(0).getKey()).isEqualTo(KEY);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        PresenceScanFilter filter = mBuilder.build();
+        assertThat(filter.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        PresenceScanFilter[] filters =
+                PresenceScanFilter.CREATOR.newArray(2);
+        assertThat(filters.length).isEqualTo(2);
     }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
index f05f65f..fa8c954 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
@@ -99,4 +99,19 @@
         assertThat(credentialElement.getKey()).isEqualTo(KEY);
         assertThat(Arrays.equals(credentialElement.getValue(), VALUE)).isTrue();
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
+    public void describeContents() {
+        PrivateCredential credential = mBuilder.build();
+        assertThat(credential.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
+    public void testCreatorNewArray() {
+        PrivateCredential[]  credentials =
+                PrivateCredential.CREATOR.newArray(2);
+        assertThat(credentials.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
index 11bbacc..774e897 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
@@ -135,6 +135,7 @@
                         .setIdentityType(IDENTITY_TYPE_PRIVATE)
                         .build();
         assertThat(credentialOne.equals((Object) credentialTwo)).isTrue();
+        assertThat(credentialOne.equals(null)).isFalse();
     }
 
     @Test
@@ -161,4 +162,19 @@
                         .build();
         assertThat(credentialOne.equals((Object) credentialTwo)).isFalse();
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        PublicCredential credential = mBuilder.build();
+        assertThat(credential.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        PublicCredential[] credentials  =
+        PublicCredential.CREATOR.newArray(2);
+        assertThat(credentials.length).isEqualTo(2);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
index 21f3d28..de4b1c3 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
@@ -171,6 +171,23 @@
         assertThat(request.getScanFilters().get(0).getMaxPathLoss()).isEqualTo(RSSI);
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        ScanRequest request = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_FAST_PAIR)
+                .build();
+        assertThat(request.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        ScanRequest[] requests =
+                ScanRequest.CREATOR.newArray(2);
+        assertThat(requests.length).isEqualTo(2);
+    }
+
     private static PresenceScanFilter getPresenceScanFilter() {
         final byte[] secretId = new byte[]{1, 2, 3, 4};
         final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
index 66bab23..506b4e2 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
@@ -63,6 +63,8 @@
             override fun onUpdated(device: NearbyDevice) {}
 
             override fun onLost(device: NearbyDevice) {}
+
+            override fun onError(errorCode: Int) {}
         }
 
         nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
index 8b19d5c..4098865 100644
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
@@ -25,13 +25,13 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
-import com.android.server.nearby.fastpair.FastPairManager
 import com.android.server.nearby.util.Environment
 import com.google.common.truth.Truth.assertThat
 
 /** UiMap for Nearby Mainline Half Sheet. */
 object NearbyHalfSheetUiMap {
     private val PACKAGE_NAME: String = getHalfSheetApkPkgName()
+    private const val ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET"
     private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
     private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
     private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
@@ -54,8 +54,7 @@
     fun getHalfSheetApkPkgName(): String {
         val appContext = ApplicationProvider.getApplicationContext<Context>()
         val resolveInfos: MutableList<ResolveInfo> =
-            appContext.packageManager.queryIntentActivities(
-                Intent(FastPairManager.ACTION_RESOURCES_APK),
+            appContext.packageManager.queryIntentActivities(Intent(ACTION_RESOURCES_APK),
                 ResolveInfoFlags.of(MATCH_SYSTEM_ONLY.toLong())
             )
 
@@ -68,4 +67,4 @@
         Log.i("NearbyHalfSheetUiMap", "Found half-sheet APK at: $halfSheetApkPkgName")
         return halfSheetApkPkgName
     }
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt
index 363355f..02847b5 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt
@@ -40,4 +40,10 @@
             putString("device", device.toString())
         }
     }
-}
\ No newline at end of file
+
+    override fun onError(errorCode: Int) {
+        postSnippetEvent(callbackId, "onError") {
+            putString("error", errorCode.toString())
+        }
+    }
+}
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index b81032d..b6c1c9d 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -22,7 +22,10 @@
     name: "NearbyMultiDevicesTestSuite",
     main: "suite_main.py",
     srcs: ["*.py"],
-    libs: ["NearbyMultiDevicesHostHelper"],
+    libs: [
+        "NearbyMultiDevicesHostHelper",
+        "mobly",
+    ],
     test_suites: [
         "general-tests",
         "mts-tethering",
@@ -38,6 +41,11 @@
         // Package the JSON metadata with the Mobly test.
         "test_data/**/*",
     ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
 }
 
 python_library_host {
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index c1f6a70..fff0ed1 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -42,11 +42,6 @@
             <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
             <option name="run-command" value="wm dismiss-keyguard" />
         </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
-          <!-- Any python dependencies can be specified and will be installed with pip -->
-          <!-- TODO(b/225958696): Import python dependencies -->
-          <option name="dep-module" value="mobly" />
-        </target_preparer>
         <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
             <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
             <option name="screen-always-on" value="on" />
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 9b35452..8a8aeab 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -29,6 +29,7 @@
         "android.test.base",
         "android.test.mock",
         "android.test.runner",
+        "HalfSheetUX",
     ],
     compile_multilib: "both",
 
diff --git a/nearby/tests/unit/AndroidManifest.xml b/nearby/tests/unit/AndroidManifest.xml
index 9f58baf..7dcb263 100644
--- a/nearby/tests/unit/AndroidManifest.xml
+++ b/nearby/tests/unit/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java b/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java
new file mode 100644
index 0000000..d095529
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 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 android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairAntispoofKeyDeviceMetadataTest {
+
+    private static final int BLE_TX_POWER  = 5;
+    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+    private static final float DELTA = 0.001f;
+    private static final int DEVICE_TYPE = 7;
+    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+    private static final byte[] IMAGE = new byte[] {7, 9};
+    private static final String IMAGE_URL = "IMAGE_URL";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+            "INITIAL_NOTIFICATION_DESCRIPTION";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+    private static final String INTENT_URI = "INTENT_URI";
+    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+    private static final float TRIGGER_DISTANCE = 111;
+    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+            "UPDATE_COMPANION_APP_DESCRIPTION";
+    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+    private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+    private static final String NAME = "NAME";
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairAntispoofKeyDeviceMetadataNotNull() {
+        FastPairDeviceMetadata fastPairDeviceMetadata = genFastPairDeviceMetadata();
+        FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+                genFastPairAntispoofKeyDeviceMetadata(ANTI_SPOOFING_KEY, fastPairDeviceMetadata);
+
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+                ANTI_SPOOFING_KEY);
+        ensureFastPairDeviceMetadataAsExpected(
+                fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairAntispoofKeyDeviceMetadataNull() {
+        FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+                genFastPairAntispoofKeyDeviceMetadata(null, null);
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+                null);
+        assertThat(fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata()).isEqualTo(
+                null);
+    }
+
+    /* Verifies DeviceMetadata. */
+    private static void ensureFastPairDeviceMetadataAsExpected(FastPairDeviceMetadata metadata) {
+        assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+        assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(metadata.getDownloadCompanionAppDescription())
+                .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getFailConnectGoToSettingsDescription())
+                .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        assertThat(metadata.getImage()).isEqualTo(IMAGE);
+        assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+        assertThat(metadata.getInitialNotificationDescription())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+        assertThat(metadata.getName()).isEqualTo(NAME);
+        assertThat(metadata.getOpenCompanionAppDescription())
+                .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getRetroactivePairingDescription())
+                .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        assertThat(metadata.getSubsequentPairingDescription())
+                .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+        assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadata.getTrueWirelessImageUrlRightBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        assertThat(metadata.getUnableToConnectDescription())
+                .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadata.getUpdateCompanionAppDescription())
+                .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getWaitLaunchCompanionAppDescription())
+                .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Generates FastPairAntispoofKeyDeviceMetadata. */
+    private static FastPairAntispoofKeyDeviceMetadata genFastPairAntispoofKeyDeviceMetadata(
+            byte[] antispoofPublicKey, FastPairDeviceMetadata deviceMetadata) {
+        FastPairAntispoofKeyDeviceMetadata.Builder builder =
+                new FastPairAntispoofKeyDeviceMetadata.Builder();
+        builder.setAntispoofPublicKey(antispoofPublicKey);
+        builder.setFastPairDeviceMetadata(deviceMetadata);
+
+        return builder.build();
+    }
+
+    /* Generates FastPairDeviceMetadata. */
+    private static FastPairDeviceMetadata genFastPairDeviceMetadata() {
+        FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+        builder.setBleTxPower(BLE_TX_POWER);
+        builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        builder.setConnectSuccessCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        builder.setDeviceType(DEVICE_TYPE);
+        builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        builder.setImage(IMAGE);
+        builder.setImageUrl(IMAGE_URL);
+        builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+        builder.setInitialNotificationDescriptionNoAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+        builder.setIntentUri(INTENT_URI);
+        builder.setName(NAME);
+        builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+        builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+        builder.setTriggerDistance(TRIGGER_DISTANCE);
+        builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+        builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+        builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+        builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+        builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+        return builder.build();
+    }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java b/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java
new file mode 100644
index 0000000..b3f2442
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2022 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 android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.accounts.Account;
+import android.content.Intent;
+import android.nearby.aidl.ByteArrayParcel;
+import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
+import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairDiscoveryItemParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
+import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
+import android.nearby.aidl.FastPairManageAccountRequestParcel;
+import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
+import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
+import android.nearby.aidl.IFastPairDataProvider;
+import android.nearby.aidl.IFastPairEligibleAccountsCallback;
+import android.nearby.aidl.IFastPairManageAccountCallback;
+import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairDataProviderServiceTest {
+
+    private static final String TAG = "FastPairDataProviderServiceTest";
+
+    private static final int BLE_TX_POWER  = 5;
+    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+    private static final float DELTA = 0.001f;
+    private static final int DEVICE_TYPE = 7;
+    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+    private static final Account ELIGIBLE_ACCOUNT_1 = new Account("abc@google.com", "type1");
+    private static final boolean ELIGIBLE_ACCOUNT_1_OPT_IN = true;
+    private static final Account ELIGIBLE_ACCOUNT_2 = new Account("def@gmail.com", "type2");
+    private static final boolean ELIGIBLE_ACCOUNT_2_OPT_IN = false;
+    private static final Account MANAGE_ACCOUNT = new Account("ghi@gmail.com", "type3");
+    private static final Account ACCOUNTDEVICES_METADATA_ACCOUNT =
+            new Account("jk@gmail.com", "type4");
+    private static final int NUM_ACCOUNT_DEVICES = 2;
+
+    private static final int ERROR_CODE_BAD_REQUEST =
+            FastPairDataProviderService.ERROR_CODE_BAD_REQUEST;
+    private static final int MANAGE_ACCOUNT_REQUEST_TYPE =
+            FastPairDataProviderService.MANAGE_REQUEST_ADD;
+    private static final String ERROR_STRING = "ERROR_STRING";
+    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+    private static final byte[] IMAGE = new byte[] {7, 9};
+    private static final String IMAGE_URL = "IMAGE_URL";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+            "INITIAL_NOTIFICATION_DESCRIPTION";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+    private static final String INTENT_URI = "INTENT_URI";
+    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+    private static final float TRIGGER_DISTANCE = 111;
+    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+            "UPDATE_COMPANION_APP_DESCRIPTION";
+    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+    private static final byte[] ACCOUNT_KEY = new byte[] {3};
+    private static final byte[] ACCOUNT_KEY_2 = new byte[] {9, 3};
+    private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[] {2, 8};
+    private static final byte[] REQUEST_MODEL_ID = new byte[] {1, 2, 3};
+    private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+    private static final String ACTION_URL = "ACTION_URL";
+    private static final int ACTION_URL_TYPE = 5;
+    private static final String APP_NAME = "APP_NAME";
+    private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[] {5, 7};
+    private static final String DESCRIPTION = "DESCRIPTION";
+    private static final String DEVICE_NAME = "DEVICE_NAME";
+    private static final String DISPLAY_URL = "DISPLAY_URL";
+    private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
+    private static final String  ICON_FIFE_URL = "ICON_FIFE_URL";
+    private static final byte[]  ICON_PNG = new byte[]{2, 5};
+    private static final String ID = "ID";
+    private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
+    private static final String MAC_ADDRESS = "MAC_ADDRESS";
+    private static final String NAME = "NAME";
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+    private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+    private static final int RSSI = 9;
+    private static final int STATE = 63;
+    private static final String TITLE = "TITLE";
+    private static final String TRIGGER_ID = "TRIGGER_ID";
+    private static final int TX_POWER = 62;
+
+    private static final int ELIGIBLE_ACCOUNTS_NUM = 2;
+    private static final ImmutableList<FastPairEligibleAccount> ELIGIBLE_ACCOUNTS =
+            ImmutableList.of(
+                    genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_1,
+                            ELIGIBLE_ACCOUNT_1_OPT_IN),
+                    genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_2,
+                            ELIGIBLE_ACCOUNT_2_OPT_IN));
+    private static final int ACCOUNTKEY_DEVICE_NUM = 2;
+    private static final ImmutableList<FastPairAccountKeyDeviceMetadata>
+            FAST_PAIR_ACCOUNT_DEVICES_METADATA =
+            ImmutableList.of(
+                    genHappyPathFastPairAccountkeyDeviceMetadata(),
+                    genHappyPathFastPairAccountkeyDeviceMetadata());
+
+    private static final FastPairAntispoofKeyDeviceMetadataRequestParcel
+            FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL =
+            genFastPairAntispoofKeyDeviceMetadataRequestParcel();
+    private static final FastPairAccountDevicesMetadataRequestParcel
+            FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL =
+            genFastPairAccountDevicesMetadataRequestParcel();
+    private static final FastPairEligibleAccountsRequestParcel
+            FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL =
+            genFastPairEligibleAccountsRequestParcel();
+    private static final FastPairManageAccountRequestParcel
+            FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL =
+            genFastPairManageAccountRequestParcel();
+    private static final FastPairManageAccountDeviceRequestParcel
+            FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL =
+            genFastPairManageAccountDeviceRequestParcel();
+    private static final FastPairAntispoofKeyDeviceMetadata
+            HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA =
+            genHappyPathFastPairAntispoofKeyDeviceMetadata();
+
+    @Captor private ArgumentCaptor<FastPairEligibleAccountParcel[]>
+            mFastPairEligibleAccountParcelsArgumentCaptor;
+    @Captor private ArgumentCaptor<FastPairAccountKeyDeviceMetadataParcel[]>
+            mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor;
+
+    @Mock private FastPairDataProviderService mMockFastPairDataProviderService;
+    @Mock private IFastPairAntispoofKeyDeviceMetadataCallback.Stub
+            mAntispoofKeyDeviceMetadataCallback;
+    @Mock private IFastPairAccountDevicesMetadataCallback.Stub mAccountDevicesMetadataCallback;
+    @Mock private IFastPairEligibleAccountsCallback.Stub mEligibleAccountsCallback;
+    @Mock private IFastPairManageAccountCallback.Stub mManageAccountCallback;
+    @Mock private IFastPairManageAccountDeviceCallback.Stub mManageAccountDeviceCallback;
+
+    private MyHappyPathProvider mHappyPathFastPairDataProvider;
+    private MyErrorPathProvider mErrorPathFastPairDataProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+
+        mHappyPathFastPairDataProvider =
+                new MyHappyPathProvider(TAG, mMockFastPairDataProviderService);
+        mErrorPathFastPairDataProvider =
+                new MyErrorPathProvider(TAG, mMockFastPairDataProviderService);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+                FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+                mAntispoofKeyDeviceMetadataCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest>
+                fastPairAntispoofKeyDeviceMetadataRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class
+                );
+        verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+                fastPairAntispoofKeyDeviceMetadataRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+        ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        final ArgumentCaptor<FastPairAntispoofKeyDeviceMetadataParcel>
+                fastPairAntispoofKeyDeviceMetadataParcelCaptor =
+                ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataParcel.class);
+        verify(mAntispoofKeyDeviceMetadataCallback).onFastPairAntispoofKeyDeviceMetadataReceived(
+                fastPairAntispoofKeyDeviceMetadataParcelCaptor.capture());
+        ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataParcelCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairAccountDevicesMetadata() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+                FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+                mAccountDevicesMetadataCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairAccountDevicesMetadataRequest>
+                fastPairAccountDevicesMetadataRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+                fastPairAccountDevicesMetadataRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+        ensureHappyPathAsExpected(fastPairAccountDevicesMetadataRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        verify(mAccountDevicesMetadataCallback).onFastPairAccountDevicesMetadataReceived(
+                mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.capture());
+        ensureHappyPathAsExpected(
+                mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathLoadFastPairEligibleAccounts() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+                FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+                mEligibleAccountsCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairEligibleAccountsRequest>
+                fastPairEligibleAccountsRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairEligibleAccountsRequest.class);
+        verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+                fastPairEligibleAccountsRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+        ensureHappyPathAsExpected(fastPairEligibleAccountsRequestCaptor.getValue());
+
+        // AOSP receives responses and verifies that it is as expected.
+        verify(mEligibleAccountsCallback).onFastPairEligibleAccountsReceived(
+                mFastPairEligibleAccountParcelsArgumentCaptor.capture());
+        ensureHappyPathAsExpected(mFastPairEligibleAccountParcelsArgumentCaptor.getValue());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathManageFastPairAccount() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().manageFastPairAccount(
+                FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+                mManageAccountCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountRequest>
+                fastPairManageAccountRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairManageAccountRequest.class);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+                fastPairManageAccountRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        ensureHappyPathAsExpected(fastPairManageAccountRequestCaptor.getValue());
+
+        // AOSP receives SUCCESS response.
+        verify(mManageAccountCallback).onSuccess();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathManageFastPairAccountDevice() throws Exception {
+        // AOSP sends calls to OEM via Parcelable.
+        mHappyPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+                FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+                mManageAccountDeviceCallback);
+
+        // OEM receives request and verifies that it is as expected.
+        final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountDeviceRequest>
+                fastPairManageAccountDeviceRequestCaptor =
+                ArgumentCaptor.forClass(
+                        FastPairDataProviderService.FastPairManageAccountDeviceRequest.class);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+                fastPairManageAccountDeviceRequestCaptor.capture(),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        ensureHappyPathAsExpected(fastPairManageAccountDeviceRequestCaptor.getValue());
+
+        // AOSP receives SUCCESS response.
+        verify(mManageAccountDeviceCallback).onSuccess();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+                FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+                mAntispoofKeyDeviceMetadataCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class),
+                any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+        verify(mAntispoofKeyDeviceMetadataCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairAccountDevicesMetadata() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+                FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+                mAccountDevicesMetadataCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class),
+                any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+        verify(mAccountDevicesMetadataCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathLoadFastPairEligibleAccounts() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+                FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+                mEligibleAccountsCallback);
+        verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+                any(FastPairDataProviderService.FastPairEligibleAccountsRequest.class),
+                any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+        verify(mEligibleAccountsCallback).onError(
+                eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathManageFastPairAccount() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().manageFastPairAccount(
+                FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+                mManageAccountCallback);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+                any(FastPairDataProviderService.FastPairManageAccountRequest.class),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        verify(mManageAccountCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testErrorPathManageFastPairAccountDevice() throws Exception {
+        mErrorPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+                FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+                mManageAccountDeviceCallback);
+        verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+                any(FastPairDataProviderService.FastPairManageAccountDeviceRequest.class),
+                any(FastPairDataProviderService.FastPairManageActionCallback.class));
+        verify(mManageAccountDeviceCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+    }
+
+    public static class MyHappyPathProvider extends FastPairDataProviderService {
+
+        private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+        public MyHappyPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+            super(tag);
+            mMockFastPairDataProviderService = mock;
+        }
+
+        public IFastPairDataProvider asProvider() {
+            Intent intent = new Intent();
+            return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+        }
+
+        @Override
+        public void onLoadFastPairAntispoofKeyDeviceMetadata(
+                @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+                @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+                    request, callback);
+            callback.onFastPairAntispoofKeyDeviceMetadataReceived(
+                    HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA);
+        }
+
+        @Override
+        public void onLoadFastPairAccountDevicesMetadata(
+                @NonNull FastPairAccountDevicesMetadataRequest request,
+                @NonNull FastPairAccountDevicesMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+                    request, callback);
+            callback.onFastPairAccountDevicesMetadataReceived(FAST_PAIR_ACCOUNT_DEVICES_METADATA);
+        }
+
+        @Override
+        public void onLoadFastPairEligibleAccounts(
+                @NonNull FastPairEligibleAccountsRequest request,
+                @NonNull FastPairEligibleAccountsCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(
+                    request, callback);
+            callback.onFastPairEligibleAccountsReceived(ELIGIBLE_ACCOUNTS);
+        }
+
+        @Override
+        public void onManageFastPairAccount(
+                @NonNull FastPairManageAccountRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+            callback.onSuccess();
+        }
+
+        @Override
+        public void onManageFastPairAccountDevice(
+                @NonNull FastPairManageAccountDeviceRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+            callback.onSuccess();
+        }
+    }
+
+    public static class MyErrorPathProvider extends FastPairDataProviderService {
+
+        private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+        public MyErrorPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+            super(tag);
+            mMockFastPairDataProviderService = mock;
+        }
+
+        public IFastPairDataProvider asProvider() {
+            Intent intent = new Intent();
+            return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+        }
+
+        @Override
+        public void onLoadFastPairAntispoofKeyDeviceMetadata(
+                @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+                @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+                    request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onLoadFastPairAccountDevicesMetadata(
+                @NonNull FastPairAccountDevicesMetadataRequest request,
+                @NonNull FastPairAccountDevicesMetadataCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+                    request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onLoadFastPairEligibleAccounts(
+                @NonNull FastPairEligibleAccountsRequest request,
+                @NonNull FastPairEligibleAccountsCallback callback) {
+            mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onManageFastPairAccount(
+                @NonNull FastPairManageAccountRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+
+        @Override
+        public void onManageFastPairAccountDevice(
+                @NonNull FastPairManageAccountDeviceRequest request,
+                @NonNull FastPairManageActionCallback callback) {
+            mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+            callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+        }
+    }
+
+    /* Generates AntispoofKeyDeviceMetadataRequestParcel. */
+    private static FastPairAntispoofKeyDeviceMetadataRequestParcel
+            genFastPairAntispoofKeyDeviceMetadataRequestParcel() {
+        FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel =
+                new FastPairAntispoofKeyDeviceMetadataRequestParcel();
+        requestParcel.modelId = REQUEST_MODEL_ID;
+
+        return requestParcel;
+    }
+
+    /* Generates AccountDevicesMetadataRequestParcel. */
+    private static FastPairAccountDevicesMetadataRequestParcel
+            genFastPairAccountDevicesMetadataRequestParcel() {
+        FastPairAccountDevicesMetadataRequestParcel requestParcel =
+                new FastPairAccountDevicesMetadataRequestParcel();
+
+        requestParcel.account = ACCOUNTDEVICES_METADATA_ACCOUNT;
+        requestParcel.deviceAccountKeys = new ByteArrayParcel[NUM_ACCOUNT_DEVICES];
+        requestParcel.deviceAccountKeys[0] = new ByteArrayParcel();
+        requestParcel.deviceAccountKeys[1] = new ByteArrayParcel();
+        requestParcel.deviceAccountKeys[0].byteArray = ACCOUNT_KEY;
+        requestParcel.deviceAccountKeys[1].byteArray = ACCOUNT_KEY_2;
+
+        return requestParcel;
+    }
+
+    /* Generates FastPairEligibleAccountsRequestParcel. */
+    private static FastPairEligibleAccountsRequestParcel
+            genFastPairEligibleAccountsRequestParcel() {
+        FastPairEligibleAccountsRequestParcel requestParcel =
+                new FastPairEligibleAccountsRequestParcel();
+        // No fields since FastPairEligibleAccountsRequestParcel is just a place holder now.
+        return requestParcel;
+    }
+
+    /* Generates FastPairManageAccountRequestParcel. */
+    private static FastPairManageAccountRequestParcel
+            genFastPairManageAccountRequestParcel() {
+        FastPairManageAccountRequestParcel requestParcel =
+                new FastPairManageAccountRequestParcel();
+        requestParcel.account = MANAGE_ACCOUNT;
+        requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+
+        return requestParcel;
+    }
+
+    /* Generates FastPairManageAccountDeviceRequestParcel. */
+    private static FastPairManageAccountDeviceRequestParcel
+            genFastPairManageAccountDeviceRequestParcel() {
+        FastPairManageAccountDeviceRequestParcel requestParcel =
+                new FastPairManageAccountDeviceRequestParcel();
+        requestParcel.account = MANAGE_ACCOUNT;
+        requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+        requestParcel.accountKeyDeviceMetadata =
+                genHappyPathFastPairAccountkeyDeviceMetadataParcel();
+
+        return requestParcel;
+    }
+
+    /* Generates Happy Path AntispoofKeyDeviceMetadata. */
+    private static FastPairAntispoofKeyDeviceMetadata
+            genHappyPathFastPairAntispoofKeyDeviceMetadata() {
+        FastPairAntispoofKeyDeviceMetadata.Builder builder =
+                new FastPairAntispoofKeyDeviceMetadata.Builder();
+        builder.setAntispoofPublicKey(ANTI_SPOOFING_KEY);
+        builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path FastPairAccountKeyDeviceMetadata. */
+    private static FastPairAccountKeyDeviceMetadata
+            genHappyPathFastPairAccountkeyDeviceMetadata() {
+        FastPairAccountKeyDeviceMetadata.Builder builder =
+                new FastPairAccountKeyDeviceMetadata.Builder();
+        builder.setDeviceAccountKey(ACCOUNT_KEY);
+        builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+        builder.setSha256DeviceAccountKeyPublicAddress(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        builder.setFastPairDiscoveryItem(genHappyPathFastPairDiscoveryItem());
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+    private static FastPairAccountKeyDeviceMetadataParcel
+            genHappyPathFastPairAccountkeyDeviceMetadataParcel() {
+        FastPairAccountKeyDeviceMetadataParcel parcel =
+                new FastPairAccountKeyDeviceMetadataParcel();
+        parcel.deviceAccountKey = ACCOUNT_KEY;
+        parcel.metadata = genHappyPathFastPairDeviceMetadataParcel();
+        parcel.sha256DeviceAccountKeyPublicAddress = SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS;
+        parcel.discoveryItem = genHappyPathFastPairDiscoveryItemParcel();
+
+        return parcel;
+    }
+
+    /* Generates Happy Path DiscoveryItem. */
+    private static FastPairDiscoveryItem genHappyPathFastPairDiscoveryItem() {
+        FastPairDiscoveryItem.Builder builder = new FastPairDiscoveryItem.Builder();
+
+        builder.setActionUrl(ACTION_URL);
+        builder.setActionUrlType(ACTION_URL_TYPE);
+        builder.setAppName(APP_NAME);
+        builder.setAuthenticationPublicKeySecp256r1(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        builder.setDescription(DESCRIPTION);
+        builder.setDeviceName(DEVICE_NAME);
+        builder.setDisplayUrl(DISPLAY_URL);
+        builder.setFirstObservationTimestampMillis(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        builder.setIconFfeUrl(ICON_FIFE_URL);
+        builder.setIconPng(ICON_PNG);
+        builder.setId(ID);
+        builder.setLastObservationTimestampMillis(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        builder.setMacAddress(MAC_ADDRESS);
+        builder.setPackageName(PACKAGE_NAME);
+        builder.setPendingAppInstallTimestampMillis(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        builder.setRssi(RSSI);
+        builder.setState(STATE);
+        builder.setTitle(TITLE);
+        builder.setTriggerId(TRIGGER_ID);
+        builder.setTxPower(TX_POWER);
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path DiscoveryItemParcel. */
+    private static FastPairDiscoveryItemParcel genHappyPathFastPairDiscoveryItemParcel() {
+        FastPairDiscoveryItemParcel parcel = new FastPairDiscoveryItemParcel();
+
+        parcel.actionUrl = ACTION_URL;
+        parcel.actionUrlType = ACTION_URL_TYPE;
+        parcel.appName = APP_NAME;
+        parcel.authenticationPublicKeySecp256r1 = AUTHENTICATION_PUBLIC_KEY_SEC_P256R1;
+        parcel.description = DESCRIPTION;
+        parcel.deviceName = DEVICE_NAME;
+        parcel.displayUrl = DISPLAY_URL;
+        parcel.firstObservationTimestampMillis = FIRST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.iconFifeUrl = ICON_FIFE_URL;
+        parcel.iconPng = ICON_PNG;
+        parcel.id = ID;
+        parcel.lastObservationTimestampMillis = LAST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.macAddress = MAC_ADDRESS;
+        parcel.packageName = PACKAGE_NAME;
+        parcel.pendingAppInstallTimestampMillis = PENDING_APP_INSTALL_TIMESTAMP_MILLIS;
+        parcel.rssi = RSSI;
+        parcel.state = STATE;
+        parcel.title = TITLE;
+        parcel.triggerId = TRIGGER_ID;
+        parcel.txPower = TX_POWER;
+
+        return parcel;
+    }
+
+    /* Generates Happy Path DeviceMetadata. */
+    private static FastPairDeviceMetadata genHappyPathFastPairDeviceMetadata() {
+        FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+        builder.setBleTxPower(BLE_TX_POWER);
+        builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        builder.setConnectSuccessCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        builder.setDeviceType(DEVICE_TYPE);
+        builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        builder.setImage(IMAGE);
+        builder.setImageUrl(IMAGE_URL);
+        builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+        builder.setInitialNotificationDescriptionNoAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+        builder.setIntentUri(INTENT_URI);
+        builder.setName(NAME);
+        builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+        builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+        builder.setTriggerDistance(TRIGGER_DISTANCE);
+        builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+        builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+        builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+        builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+        builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+        return builder.build();
+    }
+
+    /* Generates Happy Path DeviceMetadataParcel. */
+    private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+        FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+        parcel.bleTxPower = BLE_TX_POWER;
+        parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
+        parcel.connectSuccessCompanionAppNotInstalled =
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
+        parcel.deviceType = DEVICE_TYPE;
+        parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
+        parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
+        parcel.image = IMAGE;
+        parcel.imageUrl = IMAGE_URL;
+        parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
+        parcel.initialNotificationDescriptionNoAccount =
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
+        parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
+        parcel.intentUri = INTENT_URI;
+        parcel.name = NAME;
+        parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+        parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+        parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+        parcel.triggerDistance = TRIGGER_DISTANCE;
+        parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
+        parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
+        parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
+        parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
+        parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
+        parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
+        parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
+
+        return parcel;
+    }
+
+    /* Generates Happy Path FastPairEligibleAccount. */
+    private static FastPairEligibleAccount genHappyPathFastPairEligibleAccount(
+            Account account, boolean optIn) {
+        FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+        builder.setAccount(account);
+        builder.setOptIn(optIn);
+
+        return builder.build();
+    }
+
+    /* Verifies Happy Path AntispoofKeyDeviceMetadataRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest request) {
+        assertThat(request.getModelId()).isEqualTo(REQUEST_MODEL_ID);
+    }
+
+    /* Verifies Happy Path AccountDevicesMetadataRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairAccountDevicesMetadataRequest request) {
+        assertThat(request.getAccount()).isEqualTo(ACCOUNTDEVICES_METADATA_ACCOUNT);
+        assertThat(request.getDeviceAccountKeys().size()).isEqualTo(ACCOUNTKEY_DEVICE_NUM);
+        assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY);
+        assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY_2);
+    }
+
+    /* Verifies Happy Path FastPairEligibleAccountsRequest. */
+    @SuppressWarnings("UnusedVariable")
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairEligibleAccountsRequest request) {
+        // No fields since FastPairEligibleAccountsRequest is just a place holder now.
+    }
+
+    /* Verifies Happy Path FastPairManageAccountRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairManageAccountRequest request) {
+        assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+        assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+    }
+
+    /* Verifies Happy Path FastPairManageAccountDeviceRequest. */
+    private static void ensureHappyPathAsExpected(
+            FastPairDataProviderService.FastPairManageAccountDeviceRequest request) {
+        assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+        assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+        ensureHappyPathAsExpected(request.getAccountKeyDeviceMetadata());
+    }
+
+    /* Verifies Happy Path AntispoofKeyDeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.antispoofPublicKey).isEqualTo(ANTI_SPOOFING_KEY);
+        ensureHappyPathAsExpected(metadataParcel.deviceMetadata);
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel[]. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
+        assertThat(metadataParcels).isNotNull();
+        assertThat(metadataParcels).hasLength(ACCOUNTKEY_DEVICE_NUM);
+        for (FastPairAccountKeyDeviceMetadataParcel parcel: metadataParcels) {
+            ensureHappyPathAsExpected(parcel);
+        }
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.deviceAccountKey).isEqualTo(ACCOUNT_KEY);
+        assertThat(metadataParcel.sha256DeviceAccountKeyPublicAddress)
+                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        ensureHappyPathAsExpected(metadataParcel.metadata);
+        ensureHappyPathAsExpected(metadataParcel.discoveryItem);
+    }
+
+    /* Verifies Happy Path FastPairAccountKeyDeviceMetadata. */
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadata metadata) {
+        assertThat(metadata.getDeviceAccountKey()).isEqualTo(ACCOUNT_KEY);
+        assertThat(metadata.getSha256DeviceAccountKeyPublicAddress())
+                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        ensureHappyPathAsExpected(metadata.getFastPairDeviceMetadata());
+        ensureHappyPathAsExpected(metadata.getFastPairDiscoveryItem());
+    }
+
+    /* Verifies Happy Path DeviceMetadataParcel. */
+    private static void ensureHappyPathAsExpected(FastPairDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.bleTxPower).isEqualTo(BLE_TX_POWER);
+
+        assertThat(metadataParcel.connectSuccessCompanionAppInstalled).isEqualTo(
+                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadataParcel.connectSuccessCompanionAppNotInstalled).isEqualTo(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+
+        assertThat(metadataParcel.deviceType).isEqualTo(DEVICE_TYPE);
+        assertThat(metadataParcel.downloadCompanionAppDescription).isEqualTo(
+                DOWNLOAD_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.failConnectGoToSettingsDescription).isEqualTo(
+                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+
+        assertThat(metadataParcel.image).isEqualTo(IMAGE);
+        assertThat(metadataParcel.imageUrl).isEqualTo(IMAGE_URL);
+        assertThat(metadataParcel.initialNotificationDescription).isEqualTo(
+                INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadataParcel.initialNotificationDescriptionNoAccount).isEqualTo(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadataParcel.initialPairingDescription).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
+
+        assertThat(metadataParcel.name).isEqualTo(NAME);
+
+        assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
+                OPEN_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.retroactivePairingDescription).isEqualTo(
+                RETRO_ACTIVE_PAIRING_DESCRIPTION);
+
+        assertThat(metadataParcel.subsequentPairingDescription).isEqualTo(
+                SUBSEQUENT_PAIRING_DESCRIPTION);
+
+        assertThat(metadataParcel.triggerDistance).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadataParcel.trueWirelessImageUrlCase).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadataParcel.trueWirelessImageUrlLeftBud).isEqualTo(
+                TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadataParcel.trueWirelessImageUrlRightBud).isEqualTo(
+                TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+
+        assertThat(metadataParcel.unableToConnectDescription).isEqualTo(
+                UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadataParcel.unableToConnectTitle).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadataParcel.updateCompanionAppDescription).isEqualTo(
+                UPDATE_COMPANION_APP_DESCRIPTION);
+
+        assertThat(metadataParcel.waitLaunchCompanionAppDescription).isEqualTo(
+                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Verifies Happy Path DeviceMetadata. */
+    private static void ensureHappyPathAsExpected(FastPairDeviceMetadata metadata) {
+        assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+        assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+                .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(metadata.getDownloadCompanionAppDescription())
+                .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getFailConnectGoToSettingsDescription())
+                .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        assertThat(metadata.getImage()).isEqualTo(IMAGE);
+        assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+        assertThat(metadata.getInitialNotificationDescription())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+        assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+                .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+        assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+        assertThat(metadata.getName()).isEqualTo(NAME);
+        assertThat(metadata.getOpenCompanionAppDescription())
+                .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getRetroactivePairingDescription())
+                .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        assertThat(metadata.getSubsequentPairingDescription())
+                .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+        assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+        assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        assertThat(metadata.getTrueWirelessImageUrlRightBud())
+                .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        assertThat(metadata.getUnableToConnectDescription())
+                .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+        assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+        assertThat(metadata.getUpdateCompanionAppDescription())
+                .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+        assertThat(metadata.getWaitLaunchCompanionAppDescription())
+                .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+    }
+
+    /* Verifies Happy Path FastPairDiscoveryItemParcel. */
+    private static void ensureHappyPathAsExpected(FastPairDiscoveryItemParcel itemParcel) {
+        assertThat(itemParcel.actionUrl).isEqualTo(ACTION_URL);
+        assertThat(itemParcel.actionUrlType).isEqualTo(ACTION_URL_TYPE);
+        assertThat(itemParcel.appName).isEqualTo(APP_NAME);
+        assertThat(itemParcel.authenticationPublicKeySecp256r1)
+                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        assertThat(itemParcel.description).isEqualTo(DESCRIPTION);
+        assertThat(itemParcel.deviceName).isEqualTo(DEVICE_NAME);
+        assertThat(itemParcel.displayUrl).isEqualTo(DISPLAY_URL);
+        assertThat(itemParcel.firstObservationTimestampMillis)
+                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.iconFifeUrl).isEqualTo(ICON_FIFE_URL);
+        assertThat(itemParcel.iconPng).isEqualTo(ICON_PNG);
+        assertThat(itemParcel.id).isEqualTo(ID);
+        assertThat(itemParcel.lastObservationTimestampMillis)
+                .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.macAddress).isEqualTo(MAC_ADDRESS);
+        assertThat(itemParcel.packageName).isEqualTo(PACKAGE_NAME);
+        assertThat(itemParcel.pendingAppInstallTimestampMillis)
+                .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.rssi).isEqualTo(RSSI);
+        assertThat(itemParcel.state).isEqualTo(STATE);
+        assertThat(itemParcel.title).isEqualTo(TITLE);
+        assertThat(itemParcel.triggerId).isEqualTo(TRIGGER_ID);
+        assertThat(itemParcel.txPower).isEqualTo(TX_POWER);
+    }
+
+    /* Verifies Happy Path FastPairDiscoveryItem. */
+    private static void ensureHappyPathAsExpected(FastPairDiscoveryItem item) {
+        assertThat(item.getActionUrl()).isEqualTo(ACTION_URL);
+        assertThat(item.getActionUrlType()).isEqualTo(ACTION_URL_TYPE);
+        assertThat(item.getAppName()).isEqualTo(APP_NAME);
+        assertThat(item.getAuthenticationPublicKeySecp256r1())
+                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        assertThat(item.getDescription()).isEqualTo(DESCRIPTION);
+        assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
+        assertThat(item.getDisplayUrl()).isEqualTo(DISPLAY_URL);
+        assertThat(item.getFirstObservationTimestampMillis())
+                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(item.getIconFfeUrl()).isEqualTo(ICON_FIFE_URL);
+        assertThat(item.getIconPng()).isEqualTo(ICON_PNG);
+        assertThat(item.getId()).isEqualTo(ID);
+        assertThat(item.getLastObservationTimestampMillis())
+                .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(item.getMacAddress()).isEqualTo(MAC_ADDRESS);
+        assertThat(item.getPackageName()).isEqualTo(PACKAGE_NAME);
+        assertThat(item.getPendingAppInstallTimestampMillis())
+                .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        assertThat(item.getRssi()).isEqualTo(RSSI);
+        assertThat(item.getState()).isEqualTo(STATE);
+        assertThat(item.getTitle()).isEqualTo(TITLE);
+        assertThat(item.getTriggerId()).isEqualTo(TRIGGER_ID);
+        assertThat(item.getTxPower()).isEqualTo(TX_POWER);
+    }
+
+    /* Verifies Happy Path EligibleAccountParcel[]. */
+    private static void ensureHappyPathAsExpected(FastPairEligibleAccountParcel[] accountsParcel) {
+        assertThat(accountsParcel).hasLength(ELIGIBLE_ACCOUNTS_NUM);
+
+        assertThat(accountsParcel[0].account).isEqualTo(ELIGIBLE_ACCOUNT_1);
+        assertThat(accountsParcel[0].optIn).isEqualTo(ELIGIBLE_ACCOUNT_1_OPT_IN);
+
+        assertThat(accountsParcel[1].account).isEqualTo(ELIGIBLE_ACCOUNT_2);
+        assertThat(accountsParcel[1].optIn).isEqualTo(ELIGIBLE_ACCOUNT_2_OPT_IN);
+    }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java b/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java
new file mode 100644
index 0000000..edda3c2
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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 android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FastPairDeviceTest {
+    private static final String NAME = "name";
+    private static final byte[] DATA = new byte[] {0x01, 0x02};
+    private static final String MODEL_ID = "112233";
+    private static final int RSSI = -80;
+    private static final int TX_POWER = -10;
+    private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+    private static List<Integer> sMediums = new ArrayList<Integer>(List.of(1));
+    private static FastPairDevice sDevice;
+
+
+    @Before
+    public void setup() {
+        sDevice = new FastPairDevice(NAME, sMediums, RSSI, TX_POWER, MODEL_ID, MAC_ADDRESS, DATA);
+    }
+
+    @Test
+    public void testParcelable() {
+        Parcel dest = Parcel.obtain();
+        sDevice.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        FastPairDevice compareDevice = FastPairDevice.CREATOR.createFromParcel(dest);
+        assertThat(compareDevice.getName()).isEqualTo(NAME);
+        assertThat(compareDevice.getMediums()).isEqualTo(sMediums);
+        assertThat(compareDevice.getRssi()).isEqualTo(RSSI);
+        assertThat(compareDevice.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(compareDevice.getModelId()).isEqualTo(MODEL_ID);
+        assertThat(compareDevice.getBluetoothAddress()).isEqualTo(MAC_ADDRESS);
+        assertThat(compareDevice.getData()).isEqualTo(DATA);
+        assertThat(compareDevice.equals(sDevice)).isTrue();
+        assertThat(compareDevice.hashCode()).isEqualTo(sDevice.hashCode());
+    }
+
+    @Test
+    public void describeContents() {
+        assertThat(sDevice.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testToString() {
+        assertThat(sDevice.toString()).isEqualTo(
+                "FastPairDevice [name=name, medium={BLE} "
+                        + "rssi=-80 txPower=-10 "
+                        + "modelId=112233 bluetoothAddress=00:11:22:33:44:55]");
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        FastPairDevice[] fastPairDevices = FastPairDevice.CREATOR.newArray(2);
+        assertThat(fastPairDevices.length).isEqualTo(2);
+    }
+
+    @Test
+    public void testBuilder() {
+        FastPairDevice.Builder builder = new FastPairDevice.Builder();
+        FastPairDevice compareDevice = builder.setName(NAME)
+                .addMedium(1)
+                .setBluetoothAddress(MAC_ADDRESS)
+                .setRssi(RSSI)
+                .setTxPower(TX_POWER)
+                .setData(DATA)
+                .setModelId(MODEL_ID)
+                .build();
+        assertThat(compareDevice.getName()).isEqualTo(NAME);
+        assertThat(compareDevice.getMediums()).isEqualTo(sMediums);
+        assertThat(compareDevice.getRssi()).isEqualTo(RSSI);
+        assertThat(compareDevice.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(compareDevice.getModelId()).isEqualTo(MODEL_ID);
+        assertThat(compareDevice.getBluetoothAddress()).isEqualTo(MAC_ADDRESS);
+        assertThat(compareDevice.getData()).isEqualTo(DATA);
+    }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java b/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java
new file mode 100644
index 0000000..da5a518
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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 android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairEligibleAccountTest {
+
+    private static final Account ACCOUNT = new Account("abc@google.com", "type1");
+    private static final Account ACCOUNT_NULL = null;
+
+    private static final boolean OPT_IN_TRUE = true;
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairEligibleAccountNotNull() {
+        FastPairEligibleAccount eligibleAccount =
+                genFastPairEligibleAccount(ACCOUNT, OPT_IN_TRUE);
+
+        assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT);
+        assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetGetFastPairEligibleAccountNull() {
+        FastPairEligibleAccount eligibleAccount =
+                genFastPairEligibleAccount(ACCOUNT_NULL, OPT_IN_TRUE);
+
+        assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT_NULL);
+        assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+    }
+
+    /* Generates FastPairEligibleAccount. */
+    private static FastPairEligibleAccount genFastPairEligibleAccount(
+            Account account, boolean optIn) {
+        FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+        builder.setAccount(account);
+        builder.setOptIn(optIn);
+
+        return builder.build();
+    }
+}
diff --git a/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java b/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java
new file mode 100644
index 0000000..7bc6519
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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 android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public class PairStatusMetadataTest {
+    private static final int UNKNOWN = 1000;
+    private static final int SUCCESS = 1001;
+    private static final int FAIL = 1002;
+    private static final int DISMISS = 1003;
+
+    @Test
+    public void statusToString() {
+        assertThat(PairStatusMetadata.statusToString(UNKNOWN)).isEqualTo("UNKNOWN");
+        assertThat(PairStatusMetadata.statusToString(SUCCESS)).isEqualTo("SUCCESS");
+        assertThat(PairStatusMetadata.statusToString(FAIL)).isEqualTo("FAIL");
+        assertThat(PairStatusMetadata.statusToString(DISMISS)).isEqualTo("DISMISS");
+    }
+
+    @Test
+    public void getStatus() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        assertThat(pairStatusMetadata.getStatus()).isEqualTo(1001);
+        pairStatusMetadata = new PairStatusMetadata(FAIL);
+        assertThat(pairStatusMetadata.getStatus()).isEqualTo(1002);
+        pairStatusMetadata = new PairStatusMetadata(DISMISS);
+        assertThat(pairStatusMetadata.getStatus()).isEqualTo(1003);
+        pairStatusMetadata = new PairStatusMetadata(UNKNOWN);
+        assertThat(pairStatusMetadata.getStatus()).isEqualTo(1000);
+    }
+
+    @Test
+    public void testToString() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        assertThat(pairStatusMetadata.toString())
+                .isEqualTo("PairStatusMetadata[ status=SUCCESS]");
+        pairStatusMetadata = new PairStatusMetadata(FAIL);
+        assertThat(pairStatusMetadata.toString())
+                .isEqualTo("PairStatusMetadata[ status=FAIL]");
+        pairStatusMetadata = new PairStatusMetadata(DISMISS);
+        assertThat(pairStatusMetadata.toString())
+                .isEqualTo("PairStatusMetadata[ status=DISMISS]");
+        pairStatusMetadata = new PairStatusMetadata(UNKNOWN);
+        assertThat(pairStatusMetadata.toString())
+                .isEqualTo("PairStatusMetadata[ status=UNKNOWN]");
+    }
+
+    @Test
+    public void testEquals() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        PairStatusMetadata pairStatusMetadata1 = new PairStatusMetadata(SUCCESS);
+        PairStatusMetadata pairStatusMetadata2 = new PairStatusMetadata(UNKNOWN);
+        assertThat(pairStatusMetadata.equals(pairStatusMetadata1)).isTrue();
+        assertThat(pairStatusMetadata.equals(pairStatusMetadata2)).isFalse();
+        assertThat(pairStatusMetadata.hashCode()).isEqualTo(pairStatusMetadata1.hashCode());
+    }
+
+    @Test
+    public void testParcelable() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        Parcel dest = Parcel.obtain();
+        pairStatusMetadata.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        PairStatusMetadata comparStatusMetadata =
+                PairStatusMetadata.CREATOR.createFromParcel(dest);
+        assertThat(pairStatusMetadata.equals(comparStatusMetadata)).isTrue();
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        PairStatusMetadata[] pairStatusMetadatas = PairStatusMetadata.CREATOR.newArray(2);
+        assertThat(pairStatusMetadatas.length).isEqualTo(2);
+    }
+
+    @Test
+    public void describeContents() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        assertThat(pairStatusMetadata.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void  getStability() {
+        PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+        assertThat(pairStatusMetadata.getStability()).isEqualTo(0);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
index 1d3653b..c4a9729 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
@@ -20,7 +20,11 @@
 
 import static org.junit.Assert.fail;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.util.SparseArray;
 
@@ -44,6 +48,83 @@
     public static final ParcelUuid EDDYSTONE_SERVICE_DATA_PARCELUUID =
             ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
 
+    private final BleFilter mEddystoneFilter = createEddystoneFilter();
+    private final BleFilter mEddystoneUidFilter = createEddystoneUidFilter();
+    private final BleFilter mEddystoneUrlFilter = createEddystoneUrlFilter();
+    private final BleFilter mEddystoneEidFilter = createEddystoneEidFilter();
+    private final BleFilter mIBeaconWithoutUuidFilter = createIBeaconWithoutUuidFilter();
+    private final BleFilter mIBeaconWithUuidFilter = createIBeaconWithUuidFilter();
+    private final BleFilter mChromecastFilter =
+            new BleFilter.Builder().setServiceUuid(
+                    new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")))
+                    .build();
+    private final BleFilter mEddystoneWithDeviceNameFilter =
+            new BleFilter.Builder()
+                    .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+                    .setDeviceName("BERT")
+                    .build();
+    private final BleFilter mEddystoneWithDeviceAddressFilter =
+            new BleFilter.Builder()
+                    .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+                    .setDeviceAddress("00:11:22:33:AA:BB")
+                    .build();
+    private final BleFilter mServiceUuidWithMaskFilter1 =
+            new BleFilter.Builder()
+                    .setServiceUuid(
+                            new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+                            new ParcelUuid(UUID.fromString("0000000-0000-000-FFFF-FFFFFFFFFFFF")))
+                    .build();
+    private final BleFilter mServiceUuidWithMaskFilter2 =
+            new BleFilter.Builder()
+                    .setServiceUuid(
+                            new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+                            new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
+                    .build();
+
+    private final BleFilter mSmartSetupFilter =
+            new BleFilter.Builder()
+                    .setManufacturerData(
+                            BluetoothAssignedNumbers.GOOGLE,
+                            new byte[] {0x00, 0x10},
+                            new byte[] {0x00, (byte) 0xFF})
+                    .build();
+    private final BleFilter mWearFilter =
+            new BleFilter.Builder()
+                    .setManufacturerData(
+                            BluetoothAssignedNumbers.GOOGLE,
+                            new byte[] {0x00, 0x00, 0x00},
+                            new byte[] {0x00, 0x00, (byte) 0xFF})
+                    .build();
+    private final BleFilter mFakeSmartSetupSubsetFilter =
+            new BleFilter.Builder()
+                    .setManufacturerData(
+                            BluetoothAssignedNumbers.GOOGLE,
+                            new byte[] {0x00, 0x10, 0x50},
+                            new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
+                    .build();
+    private final BleFilter mFakeSmartSetupNotSubsetFilter =
+            new BleFilter.Builder()
+                    .setManufacturerData(
+                            BluetoothAssignedNumbers.GOOGLE,
+                            new byte[] {0x00, 0x10, 0x50},
+                            new byte[] {0x00, (byte) 0x00, (byte) 0xFF})
+                    .build();
+
+    private final BleFilter mFakeFilter1 =
+            new BleFilter.Builder()
+                    .setServiceData(
+                            ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+                            new byte[] {0x51, 0x64},
+                            new byte[] {0x00, (byte) 0xFF})
+                    .build();
+    private final BleFilter mFakeFilter2 =
+            new BleFilter.Builder()
+                    .setServiceData(
+                            ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+                            new byte[] {0x51, 0x64, 0x34},
+                            new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
+                    .build();
+
     private ParcelUuid mServiceDataUuid;
     private BleSighting mBleSighting;
     private BleFilter.Builder mFilterBuilder;
@@ -229,6 +310,16 @@
     }
 
     @Test
+    public void serviceDataUuidNotInBleRecord() {
+        byte[] bleRecord = FastPairTestData.eir_1;
+        byte[] serviceData = {(byte) 0xe0, (byte) 0x00};
+
+        // Verify Service Data with 2-byte UUID, no data, and NOT in scan record
+        BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
+        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+    }
+
+    @Test
     public void serviceDataMask() {
         byte[] bleRecord = FastPairTestData.sd1;
         BleFilter filter;
@@ -263,6 +354,18 @@
         mFilterBuilder.setServiceData(mServiceDataUuid, serviceData, mask).build();
     }
 
+    @Test
+    public void serviceDataMaskNotInBleRecord() {
+        byte[] bleRecord = FastPairTestData.eir_1;
+        BleFilter filter;
+
+        // Verify matching partial manufacturer with data and mask
+        byte[] serviceData1 = {(byte) 0xe0, (byte) 0x00, (byte) 0x15};
+        byte[] mask1 = {(byte) 0xff, (byte) 0xff, (byte) 0xff};
+        filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData1, mask1).build();
+        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+    }
+
 
     @Test
     public void deviceNameTest() {
@@ -280,12 +383,241 @@
         assertThat(matches(filter, null, 0, bleRecord)).isFalse();
     }
 
+    @Test
+    public void deviceNameNotInBleRecord() {
+        // Verify the name filter does not match
+        byte[] bleRecord = FastPairTestData.eir_1;
+        BleFilter filter = mFilterBuilder.setDeviceName("Pedometer").build();
+        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+    }
+
+    @Test
+    public void serviceUuid() {
+        byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+        ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
+
+        BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+        assertMatches(filter, null, 0, bleRecord);
+    }
+
+    @Test
+    public void serviceUuidNoMatch() {
+        // Verify the name filter does not match
+        byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+        ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
+
+        BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+    }
+
+    @Test
+    public void serviceUuidNotInBleRecord() {
+        // Verify the name filter does not match
+        byte[] bleRecord = FastPairTestData.eir_1;
+        ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
+
+        BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+    }
+
+    @Test
+    public void serviceUuidMask() {
+        byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+        ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
+        ParcelUuid mask = ParcelUuid.fromString("00000000-0000-0000-0000-FFFFFFFFFFFF");
+        BleFilter filter = mFilterBuilder.setServiceUuid(uuid, mask).build();
+        assertMatches(filter, null, 0, bleRecord);
+    }
+
+
+    @Test
+    public void macAddress() {
+        byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+        String macAddress = "00:11:22:33:AA:BB";
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        BluetoothDevice device = adapter.getRemoteDevice(macAddress);
+        BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
+        assertMatches(filter, device, 0, bleRecord);
+    }
+
+    @Test
+    public void macAddressNoMatch() {
+        byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+        String macAddress = "00:11:22:33:AA:00";
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        BluetoothDevice device = adapter.getRemoteDevice("00:11:22:33:AA:BB");
+        BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
+        assertThat(matches(filter, device, 0, bleRecord)).isFalse();
+    }
+
+    @Test
+    public void eddystoneIsSuperset() {
+        // Verify eddystone subtypes pass.
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneFilter)).isTrue();
+        assertThat(mEddystoneUidFilter.isSuperset(mEddystoneUidFilter)).isTrue();
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneUidFilter)).isTrue();
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneEidFilter)).isTrue();
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneUrlFilter)).isTrue();
+
+        // Non-eddystone beacon filters should never be supersets.
+        assertThat(mEddystoneFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+        assertThat(mEddystoneFilter.isSuperset(mWearFilter)).isFalse();
+        assertThat(mEddystoneFilter.isSuperset(mSmartSetupFilter)).isFalse();
+        assertThat(mEddystoneFilter.isSuperset(mChromecastFilter)).isFalse();
+        assertThat(mEddystoneFilter.isSuperset(mFakeFilter1)).isFalse();
+        assertThat(mEddystoneFilter.isSuperset(mFakeFilter2)).isFalse();
+
+        assertThat(mEddystoneUidFilter.isSuperset(mWearFilter)).isFalse();
+        assertThat(mEddystoneUidFilter.isSuperset(mSmartSetupFilter)).isFalse();
+        assertThat(mEddystoneUidFilter.isSuperset(mChromecastFilter)).isFalse();
+        assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter1)).isFalse();
+        assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter2)).isFalse();
+    }
+
+    @Test
+    public void iBeaconIsSuperset() {
+        // Verify that an iBeacon filter is a superset of itself and any filters that specify UUIDs.
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithoutUuidFilter)).isTrue();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithUuidFilter)).isTrue();
+
+        // Non-iBeacon filters should never be supersets.
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mWearFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mSmartSetupFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mChromecastFilter)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter1)).isFalse();
+        assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter2)).isFalse();
+    }
+
+    @Test
+    public void mixedFilterIsSuperset() {
+        // Compare service data vs manufacturer data filters to verify we detect supersets
+        // correctly in filters that aren't for iBeacon and Eddystone.
+        assertThat(mWearFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mEddystoneFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mEddystoneFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mEddystoneFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+
+        assertThat(mWearFilter.isSuperset(mChromecastFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mChromecastFilter)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mWearFilter)).isFalse();
+        assertThat(mChromecastFilter.isSuperset(mWearFilter)).isFalse();
+
+        assertThat(mFakeFilter1.isSuperset(mFakeFilter2)).isTrue();
+        assertThat(mFakeFilter2.isSuperset(mFakeFilter1)).isFalse();
+        assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupSubsetFilter)).isTrue();
+        assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupNotSubsetFilter)).isFalse();
+
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceNameFilter)).isTrue();
+        assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceAddressFilter)).isTrue();
+        assertThat(mEddystoneWithDeviceAddressFilter.isSuperset(mEddystoneFilter)).isFalse();
+
+        assertThat(mChromecastFilter.isSuperset(mServiceUuidWithMaskFilter1)).isTrue();
+        assertThat(mServiceUuidWithMaskFilter2.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
+        assertThat(mServiceUuidWithMaskFilter1.isSuperset(mServiceUuidWithMaskFilter2)).isTrue();
+        assertThat(mEddystoneFilter.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
+    }
+
+    @Test
+    public void toOsFilter_getTheSameFilterParameter() {
+        BleFilter nearbyFilter = createTestFilter();
+        ScanFilter osFilter = nearbyFilter.toOsFilter();
+        assertFilterValuesEqual(nearbyFilter, osFilter);
+    }
+
+    @Test
+    public void describeContents() {
+        BleFilter nearbyFilter = createTestFilter();
+        assertThat(nearbyFilter.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testHashCode() {
+        BleFilter nearbyFilter = createTestFilter();
+        BleFilter compareFilter = new BleFilter("BERT", "00:11:22:33:AA:BB",
+                new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+                new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")),
+                ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+                new byte[] {0x51, 0x64}, new byte[] {0x00, (byte) 0xFF},
+                BluetoothAssignedNumbers.GOOGLE, new byte[] {0x00, 0x10},
+                new byte[] {0x00, (byte) 0xFF});
+        assertThat(nearbyFilter.hashCode()).isEqualTo(compareFilter.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        BleFilter nearbyFilter = createTestFilter();
+        assertThat(nearbyFilter.toString()).isEqualTo("BleFilter [deviceName=BERT,"
+                + " deviceAddress=00:11:22:33:AA:BB, uuid=0000fea0-0000-1000-8000-00805f9b34fb,"
+                + " uuidMask=0fffffff-ffff-0fff-ffff-ffffffffffff,"
+                + " serviceDataUuid=0000110b-0000-1000-8000-00805f9b34fb,"
+                + " serviceData=[81, 100], serviceDataMask=[0, -1],"
+                + " manufacturerId=224, manufacturerData=[0, 16], manufacturerDataMask=[0, -1]]");
+    }
+
+    @Test
+    public void testParcel() {
+        BleFilter nearbyFilter = createTestFilter();
+        Parcel parcel = Parcel.obtain();
+        nearbyFilter.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        BleFilter compareFilter = BleFilter.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+        assertThat(compareFilter.getDeviceName()).isEqualTo("BERT");
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        BleFilter[] nearbyFilters  = BleFilter.CREATOR.newArray(2);
+        assertThat(nearbyFilters.length).isEqualTo(2);
+    }
+
     private static boolean matches(
             BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecord) {
         return filter.matches(new BleSighting(device,
                 bleRecord, rssi, 0 /* timestampNanos */));
     }
 
+    private static void assertFilterValuesEqual(BleFilter nearbyFilter, ScanFilter osFilter) {
+        assertThat(osFilter.getDeviceAddress()).isEqualTo(nearbyFilter.getDeviceAddress());
+        assertThat(osFilter.getDeviceName()).isEqualTo(nearbyFilter.getDeviceName());
+
+        assertThat(osFilter.getManufacturerData()).isEqualTo(nearbyFilter.getManufacturerData());
+        assertThat(osFilter.getManufacturerDataMask())
+                .isEqualTo(nearbyFilter.getManufacturerDataMask());
+        assertThat(osFilter.getManufacturerId()).isEqualTo(nearbyFilter.getManufacturerId());
+
+        assertThat(osFilter.getServiceData()).isEqualTo(nearbyFilter.getServiceData());
+        assertThat(osFilter.getServiceDataMask()).isEqualTo(nearbyFilter.getServiceDataMask());
+        assertThat(osFilter.getServiceDataUuid()).isEqualTo(nearbyFilter.getServiceDataUuid());
+
+        assertThat(osFilter.getServiceUuid()).isEqualTo(nearbyFilter.getServiceUuid());
+        assertThat(osFilter.getServiceUuidMask()).isEqualTo(nearbyFilter.getServiceUuidMask());
+    }
 
     private static void assertMatches(
             BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecordBytes) {
@@ -325,10 +657,10 @@
 
         // UUID match.
         if (filter.getServiceUuid() != null
-                && !matchesServiceUuids(filter.getServiceUuid(), filter.getServiceUuidMask(),
-                bleRecord.getServiceUuids())) {
-            fail("The filter specifies a service UUID but it doesn't match "
-                    + "what's in the scan record");
+                && !matchesServiceUuids(filter.getServiceUuid(),
+                filter.getServiceUuidMask(), bleRecord.getServiceUuids())) {
+            fail("The filter specifies a service UUID "
+                    + "but it doesn't match what's in the scan record");
         }
 
         // Service data match
@@ -401,6 +733,95 @@
         }
     }
 
+    private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
+        StringBuilder builder = new StringBuilder();
+        for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
+            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
+            builder.append(entry.getKey().toString());
+            builder.append(" --> ");
+            builder.append(byteString(entry.getValue()));
+        }
+        return builder.toString();
+    }
+
+    private static String byteString(SparseArray<byte[]> bytesArray) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < bytesArray.size(); i++) {
+            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
+            builder.append(byteString(bytesArray.valueAt(i)));
+        }
+        return builder.toString();
+    }
+
+    private static BleFilter createTestFilter() {
+        BleFilter.Builder builder = new BleFilter.Builder();
+        builder
+                .setServiceUuid(
+                        new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+                        new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
+                .setDeviceAddress("00:11:22:33:AA:BB")
+                .setDeviceName("BERT")
+                .setManufacturerData(
+                        BluetoothAssignedNumbers.GOOGLE,
+                        new byte[] {0x00, 0x10},
+                        new byte[] {0x00, (byte) 0xFF})
+                .setServiceData(
+                        ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+                        new byte[] {0x51, 0x64},
+                        new byte[] {0x00, (byte) 0xFF});
+        return builder.build();
+    }
+
+    // ref to beacon.decode.BeaconFilterBuilder.eddystoneFilter()
+    private static BleFilter createEddystoneFilter() {
+        return new BleFilter.Builder().setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID).build();
+    }
+    // ref to beacon.decode.BeaconFilterBuilder.eddystoneUidFilter()
+    private static BleFilter createEddystoneUidFilter() {
+        return new BleFilter.Builder()
+                .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+                .setServiceData(
+                        EDDYSTONE_SERVICE_DATA_PARCELUUID, new byte[] {(short) 0x00},
+                        new byte[] {(byte) 0xf0})
+                .build();
+    }
+
+    // ref to beacon.decode.BeaconFilterBuilder.eddystoneUrlFilter()
+    private static BleFilter createEddystoneUrlFilter() {
+        return new BleFilter.Builder()
+                .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+                .setServiceData(
+                        EDDYSTONE_SERVICE_DATA_PARCELUUID,
+                        new byte[] {(short) 0x10}, new byte[] {(byte) 0xf0})
+                .build();
+    }
+
+    // ref to beacon.decode.BeaconFilterBuilder.eddystoneEidFilter()
+    private static BleFilter createEddystoneEidFilter() {
+        return new BleFilter.Builder()
+                .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+                .setServiceData(
+                        EDDYSTONE_SERVICE_DATA_PARCELUUID,
+                        new byte[] {(short) 0x30}, new byte[] {(byte) 0xf0})
+                .build();
+    }
+
+    // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithoutUuidFilter()
+    private static BleFilter createIBeaconWithoutUuidFilter() {
+        byte[] data = {(byte) 0x02, (byte) 0x15};
+        byte[] mask = {(byte) 0xff, (byte) 0xff};
+
+        return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
+    }
+
+    // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithUuidFilter()
+    private static BleFilter createIBeaconWithUuidFilter() {
+        byte[] data = getFilterData(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
+        byte[] mask = getFilterMask(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
+
+        return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
+    }
+
     // Ref to beacon.decode.AppleBeaconDecoder.getFilterData
     private static byte[] getFilterData(ParcelUuid uuid) {
         byte[] data = new byte[18];
@@ -418,6 +839,20 @@
         return data;
     }
 
+    // Ref to beacon.decode.AppleBeaconDecoder.getFilterMask
+    private static byte[] getFilterMask(ParcelUuid uuid) {
+        byte[] mask = new byte[18];
+        mask[0] = (byte) 0xff;
+        mask[1] = (byte) 0xff;
+        // Check if UUID is needed in data
+        if (uuid != null) {
+            for (int i = 0; i < 16; i++) {
+                mask[i + 2] = (byte) 0xff;
+            }
+        }
+        return mask;
+    }
+
     // Ref to beacon.decode.AppleBeaconDecoder.uuidToByteArray
     private static byte[] uuidToByteArray(ParcelUuid uuid) {
         ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
@@ -453,24 +888,4 @@
         return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits())
                 == (data.getMostSignificantBits() & mask.getMostSignificantBits()));
     }
-
-    private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
-        StringBuilder builder = new StringBuilder();
-        for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
-            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
-            builder.append(entry.getKey().toString());
-            builder.append(" --> ");
-            builder.append(byteString(entry.getValue()));
-        }
-        return builder.toString();
-    }
-
-    private static String byteString(SparseArray<byte[]> bytesArray) {
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < bytesArray.size(); i++) {
-            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
-            builder.append(byteString(bytesArray.valueAt(i)));
-        }
-        return builder.toString();
-    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
index 5da98e2..3f9a259 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
@@ -34,6 +34,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
 import androidx.test.filters.SdkSuppress;
 
 import org.junit.Test;
@@ -238,7 +241,6 @@
         BleRecord record = BleRecord.parseFromBytes(BEACON);
         BleRecord record2 = BleRecord.parseFromBytes(SAME_BEACON);
 
-
         assertThat(record).isEqualTo(record2);
 
         // Different items.
@@ -246,5 +248,48 @@
         assertThat(record).isNotEqualTo(record2);
         assertThat(record.hashCode()).isNotEqualTo(record2.hashCode());
     }
+
+    @Test
+    public void testFields() {
+        BleRecord record = BleRecord.parseFromBytes(BEACON);
+        assertThat(byteString(record.getManufacturerSpecificData()))
+                .isEqualTo("  0215F7826DA64FA24E988024BC5B71E0893E44D02522B3");
+        assertThat(
+                byteString(record.getServiceData(
+                        ParcelUuid.fromString("000000E0-0000-1000-8000-00805F9B34FB"))))
+                .isEqualTo("[null]");
+        assertThat(record.getTxPowerLevel()).isEqualTo(-12);
+        assertThat(record.toString()).isEqualTo(
+                "BleRecord [advertiseFlags=6, serviceUuids=[], "
+                        + "manufacturerSpecificData={76=[2, 21, -9, -126, 109, -90, 79, -94, 78,"
+                        + " -104, -128, 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]},"
+                        + " serviceData={0000d00d-0000-1000-8000-00805f9b34fb"
+                        + "=[116, 109, 77, 107, 50, 54, 100]},"
+                        + " txPowerLevel=-12, deviceName=Kontakt]");
+    }
+
+    private static String byteString(SparseArray<byte[]> bytesArray) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < bytesArray.size(); i++) {
+            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
+            builder.append(byteString(bytesArray.valueAt(i)));
+        }
+        return builder.toString();
+    }
+
+    private static String byteString(byte[] bytes) {
+        if (bytes == null) {
+            return "[null]";
+        } else {
+            final char[] hexArray = "0123456789ABCDEF".toCharArray();
+            char[] hexChars = new char[bytes.length * 2];
+            for (int i = 0; i < bytes.length; i++) {
+                int v = bytes[i] & 0xFF;
+                hexChars[i * 2] = hexArray[v >>> 4];
+                hexChars[i * 2 + 1] = hexArray[v & 0x0F];
+            }
+            return new String(hexChars);
+        }
+    }
 }
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
new file mode 100644
index 0000000..d259851
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 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.ble;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/** Test for Bluetooth LE {@link BleSighting}. */
+public class BleSightingTest {
+    private static final String DEVICE_NAME = "device1";
+    private static final String OTHER_DEVICE_NAME = "device2";
+    private static final long TIME_EPOCH_MILLIS = 123456;
+    private static final long OTHER_TIME_EPOCH_MILLIS = 456789;
+    private static final int RSSI = 1;
+    private static final int OTHER_RSSI = 2;
+
+    private final BluetoothDevice mBluetoothDevice1 =
+            BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+    private final BluetoothDevice mBluetoothDevice2 =
+            BluetoothAdapter.getDefaultAdapter().getRemoteDevice("AA:BB:CC:DD:EE:FF");
+
+
+    @Test
+    public void testEquals() {
+        BleSighting sighting =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        BleSighting sighting2 =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        assertThat(sighting.equals(sighting2)).isTrue();
+        assertThat(sighting2.equals(sighting)).isTrue();
+        assertThat(sighting.hashCode()).isEqualTo(sighting2.hashCode());
+
+        // Transitive property.
+        BleSighting sighting3 =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        assertThat(sighting2.equals(sighting3)).isTrue();
+        assertThat(sighting.equals(sighting3)).isTrue();
+
+        // Set different values for each field, one at a time.
+        sighting2 = buildBleSighting(mBluetoothDevice2, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        assertSightingsNotEquals(sighting, sighting2);
+
+        sighting2 = buildBleSighting(mBluetoothDevice1, OTHER_DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        assertSightingsNotEquals(sighting, sighting2);
+
+        sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, OTHER_TIME_EPOCH_MILLIS, RSSI);
+        assertSightingsNotEquals(sighting, sighting2);
+
+        sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, OTHER_RSSI);
+        assertSightingsNotEquals(sighting, sighting2);
+    }
+
+    @Test
+    public void getNormalizedRSSI_usingNearbyRssiOffset_getCorrectValue() {
+        BleSighting sighting =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+
+        int defaultRssiOffset = 3;
+        assertThat(sighting.getNormalizedRSSI()).isEqualTo(RSSI + defaultRssiOffset);
+    }
+
+    @Test
+    public void testFields() {
+        BleSighting sighting =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        assertThat(byteString(sighting.getBleRecordBytes()))
+                .isEqualTo("080964657669636531");
+        assertThat(sighting.getRssi()).isEqualTo(RSSI);
+        assertThat(sighting.getTimestampMillis()).isEqualTo(TIME_EPOCH_MILLIS);
+        assertThat(sighting.getTimestampNanos())
+                .isEqualTo(TimeUnit.MILLISECONDS.toNanos(TIME_EPOCH_MILLIS));
+        assertThat(sighting.toString()).isEqualTo(
+                "BleSighting{device=00:11:22:33:44:55,"
+                        + " bleRecord=BleRecord [advertiseFlags=-1,"
+                        + " serviceUuids=[],"
+                        + " manufacturerSpecificData={}, serviceData={},"
+                        + " txPowerLevel=-2147483648,"
+                        + " deviceName=device1],"
+                        + " rssi=1,"
+                        + " timestampNanos=123456000000}");
+    }
+
+    @Test
+    public void testParcelable() {
+        BleSighting sighting =
+                buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+        Parcel dest = Parcel.obtain();
+        sighting.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        assertThat(sighting.getRssi()).isEqualTo(RSSI);
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        BleSighting[]  sightings =
+                BleSighting.CREATOR.newArray(2);
+        assertThat(sightings.length).isEqualTo(2);
+    }
+
+    private static String byteString(byte[] bytes) {
+        if (bytes == null) {
+            return "[null]";
+        } else {
+            final char[] hexArray = "0123456789ABCDEF".toCharArray();
+            char[] hexChars = new char[bytes.length * 2];
+            for (int i = 0; i < bytes.length; i++) {
+                int v = bytes[i] & 0xFF;
+                hexChars[i * 2] = hexArray[v >>> 4];
+                hexChars[i * 2 + 1] = hexArray[v & 0x0F];
+            }
+            return new String(hexChars);
+        }
+    }
+
+        /** Builds a BleSighting instance which will correctly match filters by device name. */
+    private static BleSighting buildBleSighting(
+            BluetoothDevice bluetoothDevice, String deviceName, long timeEpochMillis, int rssi) {
+        byte[] nameBytes = deviceName.getBytes(UTF_8);
+        byte[] bleRecordBytes = new byte[nameBytes.length + 2];
+        bleRecordBytes[0] = (byte) (nameBytes.length + 1);
+        bleRecordBytes[1] = 0x09; // Value of private BleRecord.DATA_TYPE_LOCAL_NAME_COMPLETE;
+        System.arraycopy(nameBytes, 0, bleRecordBytes, 2, nameBytes.length);
+
+        return new BleSighting(bluetoothDevice, bleRecordBytes,
+                rssi, TimeUnit.MILLISECONDS.toNanos(timeEpochMillis));
+    }
+
+    private static void assertSightingsNotEquals(BleSighting sighting1, BleSighting sighting2) {
+        assertThat(sighting1.equals(sighting2)).isFalse();
+        assertThat(sighting1.hashCode()).isNotEqualTo(sighting2.hashCode());
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java
new file mode 100644
index 0000000..9a9181d
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.ble.decode;
+
+import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class BeaconDecoderTest {
+    private BeaconDecoder mBeaconDecoder = new FastPairDecoder();;
+
+    @Test
+    public void testFields() {
+        assertThat(mBeaconDecoder.getTelemetry(parseFromBytes(getFastPairRecord()))).isNull();
+        assertThat(mBeaconDecoder.getUrl(parseFromBytes(getFastPairRecord()))).isNull();
+        assertThat(mBeaconDecoder.supportsBeaconIdAndTxPower(parseFromBytes(getFastPairRecord())))
+                .isTrue();
+        assertThat(mBeaconDecoder.supportsTxPower()).isTrue();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
index 1ad04f8..6552699 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
@@ -17,15 +17,23 @@
 package com.android.server.nearby.common.ble.decode;
 
 import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.DEVICE_ADDRESS;
 import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_MODEL_ID;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.RSSI;
 import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
 import static com.android.server.nearby.common.ble.testing.FastPairTestData.newFastPairRecord;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.server.nearby.common.ble.BleRecord;
+import com.android.server.nearby.common.ble.BleSighting;
+import com.android.server.nearby.common.ble.testing.FastPairTestData;
 import com.android.server.nearby.util.Hex;
 
 import com.google.common.primitives.Bytes;
@@ -34,12 +42,13 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class FastPairDecoderTest {
     private static final String LONG_MODEL_ID = "1122334455667788";
-    private final FastPairDecoder mDecoder = new FastPairDecoder();
     // Bits 3-6 are model ID length bits = 0b1000 = 8
     private static final byte LONG_MODEL_ID_HEADER = 0b00010000;
     private static final String PADDED_LONG_MODEL_ID = "00001111";
@@ -49,74 +58,239 @@
     private static final byte MODEL_ID_HEADER = 0b00000110;
     private static final String MODEL_ID = "112233";
     private static final byte BLOOM_FILTER_HEADER = 0b01100000;
+    private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
     private static final String BLOOM_FILTER = "112233445566";
+    private static final byte LONG_BLOOM_FILTER_HEADER = (byte) 0b10100000;
+    private static final String LONG_BLOOM_FILTER = "00112233445566778899";
     private static final byte BLOOM_FILTER_SALT_HEADER = 0b00010001;
     private static final String BLOOM_FILTER_SALT = "01";
+    private static final byte BATTERY_HEADER = 0b00110011;
+    private static final byte BATTERY_NO_NOTIFICATION_HEADER = 0b00110100;
+    private static final String BATTERY = "01048F";
     private static final byte RANDOM_RESOLVABLE_DATA_HEADER = 0b01000110;
     private static final String RANDOM_RESOLVABLE_DATA = "11223344";
-    private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
 
+    private final FastPairDecoder mDecoder = new FastPairDecoder();
+    private final BluetoothDevice mBluetoothDevice =
+            BluetoothAdapter.getDefaultAdapter().getRemoteDevice(DEVICE_ADDRESS);
+
+    @Test
+    public void filter() {
+        assertThat(FastPairDecoder.FILTER.matches(bleSighting(getFastPairRecord()))).isTrue();
+        assertThat(FastPairDecoder.FILTER.matches(bleSighting(FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD)))
+                .isTrue();
+
+        // Any ID is a valid frame.
+        assertThat(FastPairDecoder.FILTER.matches(
+                bleSighting(newFastPairRecord(Hex.stringToBytes("000001"))))).isTrue();
+        assertThat(FastPairDecoder.FILTER.matches(
+                bleSighting(newFastPairRecord(Hex.stringToBytes("098FEC"))))).isTrue();
+        assertThat(FastPairDecoder.FILTER.matches(
+                bleSighting(FastPairTestData.newFastPairRecord(
+                        LONG_MODEL_ID_HEADER, Hex.stringToBytes(LONG_MODEL_ID))))).isTrue();
+    }
 
     @Test
     public void getModelId() {
         assertThat(mDecoder.getBeaconIdBytes(parseFromBytes(getFastPairRecord())))
                 .isEqualTo(FAST_PAIR_MODEL_ID);
-        FastPairServiceData fastPairServiceData1 =
-                new FastPairServiceData(LONG_MODEL_ID_HEADER,
-                        LONG_MODEL_ID);
-        assertThat(
-                mDecoder.getBeaconIdBytes(
-                        newBleRecord(fastPairServiceData1.createServiceData())))
-                .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
         FastPairServiceData fastPairServiceData =
-                new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER,
-                        PADDED_LONG_MODEL_ID);
+                new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
         assertThat(
                 mDecoder.getBeaconIdBytes(
                         newBleRecord(fastPairServiceData.createServiceData())))
+                .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
+
+        FastPairServiceData fastPairServiceData1 =
+                new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER, PADDED_LONG_MODEL_ID);
+        assertThat(
+                mDecoder.getBeaconIdBytes(
+                        newBleRecord(fastPairServiceData1.createServiceData())))
                 .isEqualTo(Hex.stringToBytes(TRIMMED_LONG_MODEL_ID));
     }
 
     @Test
-    public void getBloomFilter() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
-                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    public void getBeaconIdType() {
+        assertThat(mDecoder.getBeaconIdType()).isEqualTo(1);
     }
 
     @Test
-    public void getBloomFilter_smallModelId() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(null, MODEL_ID);
-        assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+    public void getCalibratedBeaconTxPower() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
+        assertThat(
+                mDecoder.getCalibratedBeaconTxPower(
+                        newBleRecord(fastPairServiceData.createServiceData())))
                 .isNull();
     }
 
     @Test
-    public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+    public void getServiceDataArray() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
         assertThat(
-                FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
-                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
+                mDecoder.getServiceDataArray(
+                        newBleRecord(fastPairServiceData.createServiceData())))
+                .isEqualTo(Hex.stringToBytes("101122334455667788"));
     }
 
     @Test
-    public void getRandomResolvableData_whenContainConnectionState() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
-        fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
+    public void hasBloomFilter() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
         assertThat(
-                FastPairDecoder.getRandomResolvableData(fastPairServiceData
-                                .createServiceData()))
-                .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
+                mDecoder.hasBloomFilter(
+                        newBleRecord(fastPairServiceData.createServiceData())))
+                .isFalse();
+    }
+
+    @Test
+    public void hasModelId_allCases() {
+        // One type of the format is just the 3-byte model ID. This format has no header byte (all 3
+        // service data bytes are the model ID in little endian).
+        assertThat(hasModelId("112233", mDecoder)).isTrue();
+
+        // If the model ID is shorter than 3 bytes, then return null.
+        assertThat(hasModelId("11", mDecoder)).isFalse();
+
+        // If the data is longer than 3 bytes,
+        // byte 0 must be 0bVVVLLLLR (version, ID length, reserved).
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00001000, "11223344");
+        assertThat(
+                FastPairDecoder.hasBeaconIdBytes(
+                        newBleRecord(fastPairServiceData.createServiceData()))).isTrue();
+
+        FastPairServiceData fastPairServiceData1 =
+                new FastPairServiceData((byte) 0b00001010, "1122334455");
+        assertThat(
+                FastPairDecoder.hasBeaconIdBytes(
+                        newBleRecord(fastPairServiceData1.createServiceData()))).isTrue();
+
+        // Length bits correct, but version bits != version 0 (only supported version).
+        FastPairServiceData fastPairServiceData2 =
+                new FastPairServiceData((byte) 0b00101000, "11223344");
+        assertThat(
+                FastPairDecoder.hasBeaconIdBytes(
+                        newBleRecord(fastPairServiceData2.createServiceData()))).isFalse();
+
+        // Version bits correct, but length bits incorrect (too big, too small).
+        FastPairServiceData fastPairServiceData3 =
+                new FastPairServiceData((byte) 0b00001010, "11223344");
+        assertThat(
+                FastPairDecoder.hasBeaconIdBytes(
+                        newBleRecord(fastPairServiceData3.createServiceData()))).isFalse();
+
+        FastPairServiceData fastPairServiceData4 =
+                new FastPairServiceData((byte) 0b00000010, "11223344");
+        assertThat(
+                FastPairDecoder.hasBeaconIdBytes(
+                        newBleRecord(fastPairServiceData4.createServiceData()))).isFalse();
+    }
+
+    @Test
+    public void getBatteryLevel() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        assertThat(
+                FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBatteryLevel_notIncludedInPacket() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())).isNull();
+    }
+
+    @Test
+    public void getBatteryLevel_noModelId() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00000000, null);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        assertThat(
+                FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBatteryLevel_multipelExtraField() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBatteryLevelNoNotification() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        assertThat(
+                FastPairDecoder.getBatteryLevelNoNotification(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBatteryLevelNoNotification_notIncludedInPacket() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBatteryLevelNoNotification(
+                        fastPairServiceData.createServiceData())).isNull();
+    }
+
+    @Test
+    public void getBatteryLevelNoNotification_noModelId() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00000000, null);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        assertThat(
+                FastPairDecoder.getBatteryLevelNoNotification(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBatteryLevelNoNotification_multipleExtraField() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+        fastPairServiceData.mExtraFields.add(BATTERY);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBatteryLevelNoNotification(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BATTERY));
+    }
+
+    @Test
+    public void getBloomFilter() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
     }
 
     @Test
@@ -125,14 +299,222 @@
                 new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
         fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_NO_NOTIFICATION_HEADER);
         fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        assertThat(FastPairDecoder.getBloomFilterNoNotification(fastPairServiceData
-                        .createServiceData())).isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+        assertThat(
+                FastPairDecoder.getBloomFilterNoNotification(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilter_smallModelId() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(null, MODEL_ID);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isNull();
+    }
+
+    @Test
+    public void getBloomFilter_headerVersionBitsNotZero() {
+        // Header bits are defined as 0bVVVLLLLR (V=version, L=length, R=reserved), must be zero.
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00100000, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isNull();
+    }
+
+    @Test
+    public void getBloomFilter_noExtraFieldBytesIncluded() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(null);
+        fastPairServiceData.mExtraFields.add(null);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isNull();
+    }
+
+    @Test
+    public void getBloomFilter_extraFieldLengthIsZero() {
+        // The extra field header is formatted as 0bLLLLTTTT (L=length, T=type).
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00000000);
+        fastPairServiceData.mExtraFields.add(null);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .hasLength(0);
+    }
+
+    @Test
+    public void getBloomFilter_extraFieldLengthLongerThanPacket() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110000);
+        fastPairServiceData.mExtraFields.add("1122");
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isNull();
+    }
+
+    @Test
+    public void getBloomFilter_secondExtraFieldLengthLongerThanPacket() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100000);
+        fastPairServiceData.mExtraFields.add("1122");
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110001);
+        fastPairServiceData.mExtraFields.add("3344");
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())).isNull();
+    }
+
+    @Test
+    public void getBloomFilter_typeIsWrong() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b01100001);
+        fastPairServiceData.mExtraFields.add("112233445566");
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isNull();
+    }
+
+    @Test
+    public void getBloomFilter_noModelId() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00000000, null);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilter_noModelIdAndMultipleExtraFields() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData((byte) 0b00000000, null);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001);
+        fastPairServiceData.mExtraFields.add("00");
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilter_modelIdAndMultipleExtraFields() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+        assertThat(
+                FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
+    }
+
+    @Test
+    public void getBloomFilter_modelIdAndMultipleExtraFieldsWithBloomFilterLast() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001);
+        fastPairServiceData.mExtraFields.add("1A");
+        fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100010);
+        fastPairServiceData.mExtraFields.add("2CFE");
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilter_modelIdAndMultipleExtraFieldsWithSameType() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add("000000000000");
+        assertThat(
+                FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+    }
+
+    @Test
+    public void getBloomFilter_longExtraField() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(LONG_BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add(LONG_BLOOM_FILTER);
+        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+        fastPairServiceData.mExtraFields.add("000000000000");
+        assertThat(
+                FastPairDecoder.getBloomFilter(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(LONG_BLOOM_FILTER));
+    }
+
+    @Test
+    public void getRandomResolvableData_whenNoConnectionState() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        assertThat(
+                FastPairDecoder.getRandomResolvableData(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(null);
+    }
+
+    @Test
+    public void getRandomResolvableData_whenContainConnectionState() {
+        FastPairServiceData fastPairServiceData =
+                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+        fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
+        fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
+        assertThat(
+                FastPairDecoder.getRandomResolvableData(
+                        fastPairServiceData.createServiceData()))
+                .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
     }
 
     private static BleRecord newBleRecord(byte[] serviceDataBytes) {
         return parseFromBytes(newFastPairRecord(serviceDataBytes));
     }
-    class FastPairServiceData {
+
+    private static boolean hasModelId(String modelId, FastPairDecoder decoder) {
+        byte[] modelIdBytes = Hex.stringToBytes(modelId);
+        BleRecord bleRecord =
+                parseFromBytes(FastPairTestData.newFastPairRecord((byte) 0, modelIdBytes));
+        return FastPairDecoder.hasBeaconIdBytes(bleRecord)
+                && Arrays.equals(decoder.getBeaconIdBytes(bleRecord), modelIdBytes);
+    }
+
+    private BleSighting bleSighting(byte[] frame) {
+        return new BleSighting(mBluetoothDevice, frame, RSSI,
+                TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()));
+    }
+
+    static class FastPairServiceData {
         private Byte mHeader;
         private String mModelId;
         List<Byte> mExtraFieldHeaders = new ArrayList<>();
@@ -164,6 +546,4 @@
             return serviceData;
         }
     }
-
-
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
index ebe72b3..29f8d4e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
@@ -48,8 +48,10 @@
         // RSSI expected at 1 meter based on the calibrated tx field of -41dBm
         // Using params: distance (m), int calibratedTxPower (dBm),
         int rssi = RangingUtils.rssiFromTargetDistance(1.0, -41);
+        int rssi1 = RangingUtils.rssiFromTargetDistance(0.0, -41);
 
         assertThat(rssi).isEqualTo(-82);
+        assertThat(rssi1).isEqualTo(-41);
     }
 
     @Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java
new file mode 100644
index 0000000..6b20fdd
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 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.ble.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
+import com.android.server.nearby.common.ble.BleRecord;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class StringUtilsTest {
+    // iBeacon (Apple) Packet 1
+    private static final byte[] BEACON = {
+            // Flags
+            (byte) 0x02,
+            (byte) 0x01,
+            (byte) 0x06,
+            // Manufacturer-specific data header
+            (byte) 0x1a,
+            (byte) 0xff,
+            (byte) 0x4c,
+            (byte) 0x00,
+            // iBeacon Type
+            (byte) 0x02,
+            // Frame length
+            (byte) 0x15,
+            // iBeacon Proximity UUID
+            (byte) 0xf7,
+            (byte) 0x82,
+            (byte) 0x6d,
+            (byte) 0xa6,
+            (byte) 0x4f,
+            (byte) 0xa2,
+            (byte) 0x4e,
+            (byte) 0x98,
+            (byte) 0x80,
+            (byte) 0x24,
+            (byte) 0xbc,
+            (byte) 0x5b,
+            (byte) 0x71,
+            (byte) 0xe0,
+            (byte) 0x89,
+            (byte) 0x3e,
+            // iBeacon Instance ID (Major/Minor)
+            (byte) 0x44,
+            (byte) 0xd0,
+            (byte) 0x25,
+            (byte) 0x22,
+            // Tx Power
+            (byte) 0xb3,
+            // RSP
+            (byte) 0x08,
+            (byte) 0x09,
+            (byte) 0x4b,
+            (byte) 0x6f,
+            (byte) 0x6e,
+            (byte) 0x74,
+            (byte) 0x61,
+            (byte) 0x6b,
+            (byte) 0x74,
+            (byte) 0x02,
+            (byte) 0x0a,
+            (byte) 0xf4,
+            (byte) 0x0a,
+            (byte) 0x16,
+            (byte) 0x0d,
+            (byte) 0xd0,
+            (byte) 0x74,
+            (byte) 0x6d,
+            (byte) 0x4d,
+            (byte) 0x6b,
+            (byte) 0x32,
+            (byte) 0x36,
+            (byte) 0x64,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00
+    };
+
+    @Test
+    public void testToString() {
+        BleRecord record = BleRecord.parseFromBytes(BEACON);
+        assertThat(StringUtils.toString((SparseArray<byte[]>) null)).isEqualTo("null");
+        assertThat(StringUtils.toString(new SparseArray<byte[]>())).isEqualTo("{}");
+        assertThat(StringUtils
+                .toString((Map<ParcelUuid, byte[]>) null)).isEqualTo("null");
+        assertThat(StringUtils
+                .toString(new HashMap<ParcelUuid, byte[]>())).isEqualTo("{}");
+        assertThat(StringUtils.toString(record.getManufacturerSpecificData()))
+                .isEqualTo("{76=[2, 21, -9, -126, 109, -90, 79, -94, 78, -104, -128,"
+                        + " 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]}");
+        assertThat(StringUtils.toString(record.getServiceData()))
+                .isEqualTo("{0000d00d-0000-1000-8000-00805f9b34fb="
+                        + "[116, 109, 77, 107, 50, 54, 100]}");
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java
new file mode 100644
index 0000000..30df81f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2022 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.bloomfilter;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.concat;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit-tests for the {@link BloomFilter} class.
+ */
+public class BloomFilterTest {
+    private static final int BYTE_ARRAY_LENGTH = 100;
+
+    private final BloomFilter mBloomFilter =
+            new BloomFilter(new byte[BYTE_ARRAY_LENGTH], newHasher());
+
+    public BloomFilter.Hasher newHasher() {
+        return new FastPairBloomFilterHasher();
+    }
+
+    @Test
+    public void emptyFilter_returnsEmptyArray() throws Exception {
+        assertThat(mBloomFilter.asBytes()).isEqualTo(new byte[BYTE_ARRAY_LENGTH]);
+    }
+
+    @Test
+    public void emptyFilter_neverContains() throws Exception {
+        assertThat(mBloomFilter.possiblyContains(element(1))).isFalse();
+        assertThat(mBloomFilter.possiblyContains(element(2))).isFalse();
+        assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+    }
+
+    @Test
+    public void add() throws Exception {
+        assertThat(mBloomFilter.possiblyContains(element(1))).isFalse();
+        mBloomFilter.add(element(1));
+        assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+    }
+
+    @Test
+    public void add_onlyGivenArgAdded() throws Exception {
+        mBloomFilter.add(element(1));
+        assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+        assertThat(mBloomFilter.possiblyContains(element(2))).isFalse();
+        assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+    }
+
+    @Test
+    public void add_multipleArgs() throws Exception {
+        mBloomFilter.add(element(1));
+        mBloomFilter.add(element(2));
+        assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+        assertThat(mBloomFilter.possiblyContains(element(2))).isTrue();
+        assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+    }
+
+    /**
+     * This test was added because of a bug where the BloomFilter doesn't utilize all bits given.
+     * Functionally, the filter still works, but we just have a much higher false positive rate. The
+     * bug was caused by confusing bit length and byte length, which made our BloomFilter only set
+     * bits on the first byteLength (bitLength / 8) bits rather than the whole bitLength bits.
+     *
+     * <p>Here, we're verifying that the bits set are somewhat scattered. So instead of something
+     * like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting something like
+     * [ 0, 1, 0, 0, 1, 1, 0, 0,0, 1, ..., 1, 0].
+     */
+    @Test
+    public void randomness_noEndBias() throws Exception {
+        // Add one element to our BloomFilter.
+        mBloomFilter.add(element(1));
+
+        // Record the amount of non-zero bytes and the longest streak of zero bytes in the resulting
+        // BloomFilter. This is an approximation of reasonable distribution since we're recording by
+        // bytes instead of bits.
+        int nonZeroCount = 0;
+        int longestZeroStreak = 0;
+        int currentZeroStreak = 0;
+        for (byte b : mBloomFilter.asBytes()) {
+            if (b == 0) {
+                currentZeroStreak++;
+            } else {
+                // Increment the number of non-zero bytes we've seen, update the longest zero
+                // streak, and then reset the current zero streak.
+                nonZeroCount++;
+                longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak);
+                currentZeroStreak = 0;
+            }
+        }
+        // Update the longest zero streak again for the tail case.
+        longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak);
+
+        // Since randomness is hard to measure within one unit test, we instead do a valid check.
+        // All non-zero bytes should not be packed into one end of the array.
+        //
+        // In this case, the size of one end is approximated to be:
+        //   BYTE_ARRAY_LENGTH / nonZeroCount.
+        // Therefore, the longest zero streak should be less than:
+        //   BYTE_ARRAY_LENGTH - one end of the array.
+        int longestAcceptableZeroStreak = BYTE_ARRAY_LENGTH - (BYTE_ARRAY_LENGTH / nonZeroCount);
+        assertThat(longestZeroStreak).isAtMost(longestAcceptableZeroStreak);
+    }
+
+    @Test
+    public void randomness_falsePositiveRate() throws Exception {
+        // Create a new BloomFilter with a length of only 10 bytes.
+        BloomFilter bloomFilter = new BloomFilter(new byte[10], newHasher());
+
+        // Add 5 distinct elements to the BloomFilter.
+        for (int i = 0; i < 5; i++) {
+            bloomFilter.add(element(i));
+        }
+
+        // Now test 100 other elements and record the number of false positives.
+        int falsePositives = 0;
+        for (int i = 5; i < 105; i++) {
+            falsePositives += bloomFilter.possiblyContains(element(i)) ? 1 : 0;
+        }
+
+        // We expect the false positive rate to be 3% with 5 elements in a 10 byte filter. Thus,
+        // we give a little leeway and verify that the false positive rate is no more than 5%.
+        assertWithMessage(
+                String.format(
+                        "False positive rate too large. Expected <= 5%%, but got %d%%.",
+                        falsePositives))
+                .that(falsePositives <= 5)
+                .isTrue();
+        System.out.printf("False positive rate: %d%%%n", falsePositives);
+    }
+
+
+    private String element(int index) {
+        return "ELEMENT_" + index;
+    }
+
+    @Test
+    public void specificBitPattern() throws Exception {
+        // Create a new BloomFilter along with a fixed set of elements
+        // and bit patterns to verify with.
+        BloomFilter bloomFilter = new BloomFilter(new byte[6], newHasher());
+        // Combine an account key and mac address.
+        byte[] element =
+                concat(
+                        base16().decode("11223344556677889900AABBCCDDEEFF"),
+                        base16().withSeparator(":", 2).decode("84:68:3E:00:02:11"));
+        byte[] expectedBitPattern = new byte[] {0x50, 0x00, 0x04, 0x15, 0x08, 0x01};
+
+        // Add the fixed elements to the filter.
+        bloomFilter.add(element);
+
+        // Verify that the resulting bytes match the expected one.
+        byte[] bloomFilterBytes = bloomFilter.asBytes();
+        assertWithMessage(
+                "Unexpected bit pattern. Expected %s, but got %s.",
+                base16().encode(expectedBitPattern), base16().encode(bloomFilterBytes))
+                .that(Arrays.equals(expectedBitPattern, bloomFilterBytes))
+                .isTrue();
+
+        // Verify that the expected bit pattern creates a BloomFilter containing all fixed elements.
+        bloomFilter = new BloomFilter(expectedBitPattern, newHasher());
+        assertThat(bloomFilter.possiblyContains(element)).isTrue();
+    }
+
+    // This test case has been on the spec,
+    // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases.
+    // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble
+    // address) to clarify test results with partners.
+    @Test
+    public void specificBitPattern_hasOneAccountKey() {
+        BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher());
+        BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher());
+        byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF");
+        byte[] salt1 = base16().decode("C7");
+        byte[] salt2 = base16().decode("C7C8");
+
+        // Add the fixed elements to the filter.
+        bloomFilter1.add(concat(accountKey, salt1));
+        bloomFilter2.add(concat(accountKey, salt2));
+
+        assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("0A428810"));
+        assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("020C802A"));
+    }
+
+    // Adds this test case to spec. We can easily change the parameters (e.g. account key, ble
+    // address, battery data) to clarify test results with partners.
+    @Test
+    public void specificBitPattern_hasOneAccountKey_withBatteryData() {
+        BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher());
+        BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher());
+        byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF");
+        byte[] salt1 = base16().decode("C7");
+        byte[] salt2 = base16().decode("C7C8");
+        byte[] batteryData = {
+                0b00110011, // length = 3, show UI indication.
+                0b01000000, // Left bud: not charging, battery level = 64.
+                0b01000000, // Right bud: not charging, battery level = 64.
+                0b01000000 // Case: not charging, battery level = 64.
+        };
+
+        // Adds battery data to build bloom filter.
+        bloomFilter1.add(concat(accountKey, salt1, batteryData));
+        bloomFilter2.add(concat(accountKey, salt2, batteryData));
+
+        assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("4A00F000"));
+        assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("0101460A"));
+    }
+
+    // This test case has been on the spec,
+    // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases.
+    // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble
+    // address) to clarify test results with partners.
+    @Test
+    public void specificBitPattern_hasTwoAccountKeys() {
+        BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+        BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+        byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+        byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+        byte[] salt1 = base16().decode("C7");
+        byte[] salt2 = base16().decode("C7C8");
+
+        // Adds the fixed elements to the filter.
+        bloomFilter1.add(concat(accountKey1, salt1));
+        bloomFilter1.add(concat(accountKey2, salt1));
+        bloomFilter2.add(concat(accountKey1, salt2));
+        bloomFilter2.add(concat(accountKey2, salt2));
+
+        assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("2FBA064200"));
+        assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("844A62208B"));
+    }
+
+    // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble
+    // address, battery data) to clarify test results with partners.
+    @Test
+    public void specificBitPattern_hasTwoAccountKeys_withBatteryData() {
+        BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+        BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+        byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+        byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+        byte[] salt1 = base16().decode("C7");
+        byte[] salt2 = base16().decode("C7C8");
+        byte[] batteryData = {
+                0b00110011, // length = 3, show UI indication.
+                0b01000000, // Left bud: not charging, battery level = 64.
+                0b01000000, // Right bud: not charging, battery level = 64.
+                0b01000000 // Case: not charging, battery level = 64.
+        };
+
+        // Adds battery data to build bloom filter.
+        bloomFilter1.add(concat(accountKey1, salt1, batteryData));
+        bloomFilter1.add(concat(accountKey2, salt1, batteryData));
+        bloomFilter2.add(concat(accountKey1, salt2, batteryData));
+        bloomFilter2.add(concat(accountKey2, salt2, batteryData));
+
+        assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("102256C04D"));
+        assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("461524D008"));
+    }
+
+    // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble
+    // address, battery data and battery remaining time) to clarify test results with partners.
+    @Test
+    public void specificBitPattern_hasTwoAccountKeys_withBatteryLevelAndRemainingTime() {
+        BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+        BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+        byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+        byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+        byte[] salt1 = base16().decode("C7");
+        byte[] salt2 = base16().decode("C7C8");
+        byte[] batteryData = {
+                0b00110011, // length = 3, show UI indication.
+                0b01000000, // Left bud: not charging, battery level = 64.
+                0b01000000, // Right bud: not charging, battery level = 64.
+                0b01000000 // Case: not charging, battery level = 64.
+        };
+        byte[] batteryRemainingTime = {
+                0b00010101, // length = 1, type = 0b0101 (remaining battery time).
+                0x1E, // remaining battery time (in minutes) =  30 minutes.
+        };
+
+        // Adds battery data to build bloom filter.
+        bloomFilter1.add(concat(accountKey1, salt1, batteryData, batteryRemainingTime));
+        bloomFilter1.add(concat(accountKey2, salt1, batteryData, batteryRemainingTime));
+        bloomFilter2.add(concat(accountKey1, salt2, batteryData, batteryRemainingTime));
+        bloomFilter2.add(concat(accountKey2, salt2, batteryData, batteryRemainingTime));
+
+        assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("32A086B41A"));
+        assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("C2A042043E"));
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java
new file mode 100644
index 0000000..92c3b1a
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.bloomfilter;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+
+public class FastPairBloomFilterHasherTest {
+    private static final Charset CHARSET = UTF_8;
+    private static FastPairBloomFilterHasher sFastPairBloomFilterHasher =
+            new FastPairBloomFilterHasher();
+    @Test
+    public void getHashes() {
+        int[] hashe1 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET));
+        int[] hashe2 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET));
+        int[] hashe3 = sFastPairBloomFilterHasher.getHashes(element(2).getBytes(CHARSET));
+        assertThat(hashe1).isEqualTo(hashe2);
+        assertThat(hashe1).isNotEqualTo(hashe3);
+    }
+
+    private String element(int index) {
+        return "ELEMENT_" + index;
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
index 28d2fca..cf40fc6 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
@@ -63,6 +63,21 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void inputNullSecretKeyToEncode_mustThrowException() {
+        byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
+
+        GeneralSecurityException exception =
+                assertThrows(
+                        GeneralSecurityException.class,
+                        () -> AdditionalDataEncoder.encodeAdditionalDataPacket(null, rawData));
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("Incorrect secret for encoding additional data packet");
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void inputIncorrectKeySizeToEncode_mustThrowException() {
         byte[] secret = new byte[KEY_LENGTH - 1];
         byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
@@ -79,6 +94,54 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void inputNullAdditionalDataToEncode_mustThrowException()
+            throws GeneralSecurityException {
+        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+
+        GeneralSecurityException exception =
+                assertThrows(
+                        GeneralSecurityException.class,
+                        () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, null));
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("Invalid data for encoding additional data packet");
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void inputInvalidAdditionalDataSizeToEncode_mustThrowException()
+            throws GeneralSecurityException {
+        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+        byte[] rawData = base16().decode("");
+        GeneralSecurityException exception =
+                assertThrows(
+                        GeneralSecurityException.class,
+                        () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData));
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("Invalid data for encoding additional data packet");
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void inputTooLargeAdditionalDataSizeToEncode_mustThrowException()
+            throws GeneralSecurityException {
+        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+        byte[] rawData = new byte[MAX_LENGTH_OF_DATA + 1];
+        GeneralSecurityException exception =
+                assertThrows(
+                        GeneralSecurityException.class,
+                        () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData));
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("Invalid data for encoding additional data packet");
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void inputIncorrectKeySizeToDecode_mustThrowException() {
         byte[] secret = new byte[KEY_LENGTH - 1];
         byte[] packet = base16().decode("01234567890123456789");
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
index 0a56f2f..6871024 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
@@ -84,6 +84,23 @@
     }
 
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void getDevice() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        try {
+            assertThat(new BluetoothAudioPairer(
+                    context,
+                    BLUETOOTH_DEVICE,
+                    Preferences.builder().build(),
+                    new EventLoggerWrapper(new TestEventLogger()),
+                    null /* KeyBasePairingInfo */,
+                    null /*PasskeyConfirmationHandler */,
+                    new TimingLogger(EVENT_NAME, Preferences.builder().build())).getDevice())
+                    .isEqualTo(BLUETOOTH_DEVICE);
+        } catch (PairingException e) {
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testBluetoothAudioPairerUnpairNoCrash() {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         try {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
new file mode 100644
index 0000000..48f4b9b
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class BluetoothClassicPairerTest {
+    @Mock
+    PasskeyConfirmationHandler mPasskeyConfirmationHandler;
+    private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
+            .getRemoteDevice("11:22:33:44:55:66");
+    private  BluetoothClassicPairer mBluetoothClassicPairer;
+
+    @Before
+    public void setUp() {
+        mBluetoothClassicPairer = new BluetoothClassicPairer(
+                ApplicationProvider.getApplicationContext(),
+                BLUETOOTH_DEVICE,
+                Preferences.builder().build(),
+                mPasskeyConfirmationHandler);
+    }
+
+    @Test
+    public void pair() throws PairingException {
+        PairingException exception =
+                assertThrows(
+                        PairingException.class,
+                        () -> mBluetoothClassicPairer.pair());
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("BluetoothClassicPairer, createBond");
+        assertThat(mBluetoothClassicPairer.isPaired()).isFalse();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java
new file mode 100644
index 0000000..9450d60
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import junit.framework.TestCase;
+
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link Bytes}.
+ */
+public class BytesTest extends TestCase {
+
+    private static final Bytes.Value VALUE1 =
+            new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN);
+    private static final Bytes.Value VALUE2 =
+            new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN);
+    private static final Bytes.Value VALUE3 =
+            new Bytes.Value(new byte[]{1, 3}, ByteOrder.BIG_ENDIAN);
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEquals_asExpected()  {
+        assertThat(VALUE1.equals(VALUE2)).isTrue();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNotEquals_asExpected()  {
+        assertThat(VALUE1.equals(VALUE3)).isFalse();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBytes_asExpected()  {
+        assertThat(Arrays.equals(VALUE1.getBytes(ByteOrder.BIG_ENDIAN), new byte[]{1, 2})).isTrue();
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testToString()  {
+        assertThat(VALUE1.toString()).isEqualTo("0102");
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReverse()  {
+        assertThat(VALUE1.reverse(new byte[]{1, 2})).isEqualTo(new byte[]{2, 1});
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
index f7ffa24..6684fbc 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
@@ -33,6 +33,8 @@
 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
 
+import com.google.common.collect.ImmutableList;
+
 import junit.framework.TestCase;
 
 import org.mockito.Mock;
@@ -44,6 +46,8 @@
  */
 public class ConstantsTest extends TestCase {
 
+    private static final int PASSKEY = 32689;
+
     @Mock
     private BluetoothGattConnection mMockGattConnection;
 
@@ -78,4 +82,62 @@
         assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
                 .isEqualTo(OLD_KEY_BASE_PAIRING_CHARACTERISTICS);
     }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_accountKeyCharacteristic_notCrash() throws BluetoothException {
+        Constants.FastPairService.AccountKeyCharacteristic.getId(mMockGattConnection);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_additionalDataCharacteristic_notCrash() throws BluetoothException {
+        Constants.FastPairService.AdditionalDataCharacteristic.getId(mMockGattConnection);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_nameCharacteristic_notCrash() throws BluetoothException {
+        Constants.FastPairService.NameCharacteristic.getId(mMockGattConnection);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_passKeyCharacteristic_encryptDecryptSuccessfully()
+            throws java.security.GeneralSecurityException {
+        byte[] secret = AesEcbSingleBlockEncryption.generateKey();
+
+        Constants.FastPairService.PasskeyCharacteristic.getId(mMockGattConnection);
+        assertThat(
+                Constants.FastPairService.PasskeyCharacteristic.decrypt(
+                        Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER,
+                        secret,
+                        Constants.FastPairService.PasskeyCharacteristic.encrypt(
+                                Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER,
+                                secret,
+                                PASSKEY))
+        ).isEqualTo(PASSKEY);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_beaconActionsCharacteristic_notCrash() throws BluetoothException {
+        Constants.FastPairService.BeaconActionsCharacteristic.getId(mMockGattConnection);
+        for (byte b : ImmutableList.of(
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .READ_BEACON_PARAMETERS,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .READ_PROVISIONING_STATE,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .SET_EPHEMERAL_IDENTITY_KEY,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .CLEAR_EPHEMERAL_IDENTITY_KEY,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .READ_EPHEMERAL_IDENTITY_KEY,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .RING,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .READ_RINGING_STATE,
+                (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+                        .UNKNOWN
+        )) {
+            assertThat(Constants.FastPairService.BeaconActionsCharacteristic
+                    .valueOf(b)).isEqualTo(b);
+        }
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java
new file mode 100644
index 0000000..052e696
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.intdefs.FastPairEventIntDefs;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CreateBondException}.
+ */
+public class CreateBondExceptionTest extends TestCase {
+
+    private static final String FORMAT = "FORMAT";
+    private static final int REASON = 0;
+    private static final CreateBondException EXCEPTION = new CreateBondException(
+            FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT, REASON, FORMAT);
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_getter_asExpected() throws BluetoothException {
+        assertThat(EXCEPTION.getErrorCode()).isEqualTo(
+                FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT);
+        assertThat(EXCEPTION.getReason()).isSameInstanceAs(REASON);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java
new file mode 100644
index 0000000..94bf111
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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 org.mockito.MockitoAnnotations.initMocks;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link DeviceIntentReceiver}.
+ */
+public class DeviceIntentReceiverTest extends TestCase {
+    @Mock Preferences mPreferences;
+    @Mock BluetoothDevice mBluetoothDevice;
+
+    private DeviceIntentReceiver mDeviceIntentReceiver;
+    private Intent mIntent;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        initMocks(this);
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mDeviceIntentReceiver = DeviceIntentReceiver.oneShotReceiver(
+                context, mPreferences, mBluetoothDevice);
+
+        mIntent = new Intent().putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+    }
+
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_onReceive_notCrash() throws Exception {
+        mDeviceIntentReceiver.onReceive(mIntent);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
index 28e925f..1b63ad0 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
@@ -58,6 +58,13 @@
                         .setBluetoothDevice(BLUETOOTH_DEVICE)
                         .setProfile(PROFILE)
                         .build();
+        assertThat(event.hasBluetoothDevice()).isTrue();
+        assertThat(event.hasProfile()).isTrue();
+        assertThat(event.isFailure()).isTrue();
+        assertThat(event.toString()).isEqualTo(
+                "Event{eventCode=1120, timestamp=1234, profile=1, "
+                        + "bluetoothDevice=11:22:33:44:55:66, "
+                        + "exception=java.lang.Exception: Test exception}");
 
         Parcel parcel = Parcel.obtain();
         event.writeToParcel(parcel, event.describeContents());
@@ -70,5 +77,8 @@
         assertThat(result.getEventCode()).isEqualTo(event.getEventCode());
         assertThat(result.getBluetoothDevice()).isEqualTo(event.getBluetoothDevice());
         assertThat(result.getProfile()).isEqualTo(event.getProfile());
+        assertThat(result.equals(event)).isTrue();
+
+        assertThat(Event.CREATOR.newArray(10)).isNotEmpty();
     }
 }
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 a103a72..63c39b2 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
@@ -23,9 +23,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -45,6 +47,7 @@
 
 import junit.framework.TestCase;
 
+import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -75,6 +78,8 @@
     private TestEventLogger mEventLogger;
     @Mock private TimingLogger mTimingLogger;
     @Mock private BluetoothAudioPairer mBluetoothAudioPairer;
+    @Mock private android.bluetooth.BluetoothAdapter mBluetoothAdapter;
+    @Mock FastPairDualConnection mFastPairDualConnection;
 
     @Override
     public void setUp() throws Exception {
@@ -287,6 +292,23 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReflectionException()
+            throws BluetoothException, ReflectionException, ExecutionException,
+            InterruptedException, PairingException, TimeoutException {
+        when(mFastPairDualConnection.pair())
+                .thenThrow(new ReflectionException(
+                        new NoSuchMethodException("testReflectionException")));
+        ReflectionException exception =
+                assertThrows(
+                        ReflectionException.class,
+                        () -> mFastPairDualConnection.pair());
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("testReflectionException");
+    }
+
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testHistoryItem() {
         FastPairDualConnection connection = new FastPairDualConnection(
@@ -307,6 +329,11 @@
                 .isFalse();
     }
 
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetLeState() throws ReflectionException {
+        FastPairDualConnection.getLeState(mBluetoothAdapter);
+    }
+
     static class TestEventLogger implements EventLogger {
 
         private List<Item> mLogs = new ArrayList<>();
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java
new file mode 100644
index 0000000..e53e60f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2022 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.Constants.BLUETOOTH_ADDRESS_LENGTH;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.BluetoothGattException;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request;
+import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
+import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
+import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
+import com.android.server.nearby.intdefs.NearbyEventIntDefs;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.security.GeneralSecurityException;
+import java.time.Duration;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link HandshakeHandler}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandshakeHandlerTest {
+
+    public static final byte[] PUBLIC_KEY =
+            BaseEncoding.base64().decode(
+                    "d2JTfvfdS6u7LmGfMOmco3C7ra3lW1k17AOly0LrBydDZURacfTY"
+                            + "IMmo5K1ejfD9e8b6qHsDTNzselhifi10kQ==");
+    private static final String SEEKER_ADDRESS = "A1:A2:A3:A4:A5:A6";
+    private static final String PROVIDER_BLE_ADDRESS = "11:22:33:44:55:66";
+    /**
+     * The random-resolvable private address (RPA) is sometimes used when advertising over BLE, to
+     * hide the static public address (otherwise, the Fast Pair device would b
+     * identifiable/trackable whenever it's BLE advertising).
+     */
+    private static final String RANDOM_PRIVATE_ADDRESS = "BB:BB:BB:BB:BB:1E";
+    private static final byte[] SHARED_SECRET =
+            BaseEncoding.base16().decode("0123456789ABCDEF0123456789ABCDEF");
+
+    @Mock EventLoggerWrapper mEventLoggerWrapper;
+    @Mock BluetoothGattConnection mBluetoothGattConnection;
+    @Mock BluetoothGattConnection.ChangeObserver mChangeObserver;
+    @Mock private Consumer<Integer> mRescueFromError;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void handshakeGattError_noRetryError_failed() throws BluetoothException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .build();
+        BluetoothGattException exception =
+                new BluetoothGattException("Exception for no retry", 257);
+        when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+        GattConnectionManager gattConnectionManager =
+                createGattConnectionManager(Preferences.builder(), () -> {});
+        gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+        when(mBluetoothGattConnection.enableNotification(any(), any()))
+                .thenReturn(mChangeObserver);
+        InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+        assertThrows(
+                BluetoothGattException.class,
+                () ->
+                        getHandshakeHandler(gattConnectionManager, address -> address)
+                                .doHandshakeWithRetryAndSignalLostCheck(
+                                        PUBLIC_KEY,
+                                        keyBasedPairingRequest,
+                                        mRescueFromError));
+
+        inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
+                NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void handshakeGattError_retryAndNoCount_throwException() throws BluetoothException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .build();
+        BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+        when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+        GattConnectionManager gattConnectionManager =
+                createGattConnectionManager(Preferences.builder(), () -> {});
+        gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+        when(mBluetoothGattConnection.enableNotification(any(), any()))
+                .thenReturn(mChangeObserver);
+        InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+        HandshakeHandler.HandshakeException handshakeException =
+                assertThrows(
+                        HandshakeHandler.HandshakeException.class,
+                        () -> getHandshakeHandler(gattConnectionManager, address -> address)
+                                .doHandshakeWithRetryAndSignalLostCheck(
+                                        PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verifyNoMoreInteractions();
+        assertThat(handshakeException.getOriginalException()).isEqualTo(exception);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void handshakeGattError_noRetryOnTimeout_throwException() throws BluetoothException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .build();
+        BluetoothOperationExecutor.BluetoothOperationTimeoutException exception =
+                new BluetoothOperationExecutor.BluetoothOperationTimeoutException("Test timeout");
+        when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+        GattConnectionManager gattConnectionManager =
+                createGattConnectionManager(Preferences.builder(), () -> {});
+        gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+        when(mBluetoothGattConnection.enableNotification(any(), any()))
+                .thenReturn(mChangeObserver);
+        InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+        assertThrows(
+                HandshakeHandler.HandshakeException.class,
+                () ->
+                        new HandshakeHandler(
+                                gattConnectionManager,
+                                PROVIDER_BLE_ADDRESS,
+                                Preferences.builder().setRetrySecretHandshakeTimeout(false).build(),
+                                mEventLoggerWrapper,
+                                address -> address)
+                                .doHandshakeWithRetryAndSignalLostCheck(
+                                        PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void handshakeGattError_signalLost() throws BluetoothException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .build();
+        BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+        when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+        GattConnectionManager gattConnectionManager =
+                createGattConnectionManager(Preferences.builder(), () -> {});
+        gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+        when(mBluetoothGattConnection.enableNotification(any(), any()))
+                .thenReturn(mChangeObserver);
+        InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+        SignalLostException signalLostException =
+                assertThrows(
+                        SignalLostException.class,
+                        () -> getHandshakeHandler(gattConnectionManager, address -> null)
+                                .doHandshakeWithRetryAndSignalLostCheck(
+                                        PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+        inOrder.verify(mEventLoggerWrapper)
+                .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        assertThat(signalLostException).hasCauseThat().isEqualTo(exception);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void handshakeGattError_addressRotate() throws BluetoothException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .build();
+        BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+        when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+        GattConnectionManager gattConnectionManager =
+                createGattConnectionManager(Preferences.builder(), () -> {});
+        gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+        when(mBluetoothGattConnection.enableNotification(any(), any()))
+                .thenReturn(mChangeObserver);
+        InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+        SignalRotatedException signalRotatedException =
+                assertThrows(
+                        SignalRotatedException.class,
+                        () -> getHandshakeHandler(
+                                gattConnectionManager, address -> "AA:BB:CC:DD:EE:FF")
+                                .doHandshakeWithRetryAndSignalLostCheck(
+                                        PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+        inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
+                NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+        inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+        assertThat(signalRotatedException.getNewAddress()).isEqualTo("AA:BB:CC:DD:EE:FF");
+        assertThat(signalRotatedException).hasCauseThat().isEqualTo(exception);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void constructBytes_setRetroactiveFlag_decodeCorrectly() throws
+            GeneralSecurityException {
+        HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+                new HandshakeHandler.KeyBasedPairingRequest.Builder()
+                        .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+                        .addFlag(REQUEST_RETROACTIVE_PAIR)
+                        .setSeekerPublicAddress(BluetoothAddress.decode(SEEKER_ADDRESS))
+                        .build();
+
+        byte[] encryptedRawMessage =
+                AesEcbSingleBlockEncryption.encrypt(
+                        SHARED_SECRET, keyBasedPairingRequest.getBytes());
+        HandshakeRequest handshakeRequest =
+                new HandshakeRequest(SHARED_SECRET, encryptedRawMessage);
+
+        assertThat(handshakeRequest.getType())
+                .isEqualTo(HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST);
+        assertThat(handshakeRequest.requestRetroactivePair()).isTrue();
+        assertThat(handshakeRequest.getVerificationData())
+                .isEqualTo(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS));
+        assertThat(handshakeRequest.getSeekerPublicAddress())
+                .isEqualTo(BluetoothAddress.decode(SEEKER_ADDRESS));
+        assertThat(handshakeRequest.requestDeviceName()).isFalse();
+        assertThat(handshakeRequest.requestDiscoverable()).isFalse();
+        assertThat(handshakeRequest.requestProviderInitialBonding()).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void getTimeout_notOverShortRetryMaxSpentTime_getShort() {
+        Preferences preferences = Preferences.builder().build();
+
+        assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
+                .getTimeoutMs(
+                        preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
+                                - 1))
+                .isEqualTo(preferences.getSecretHandshakeShortTimeoutMs());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void getTimeout_overShortRetryMaxSpentTime_getLong() {
+        Preferences preferences = Preferences.builder().build();
+
+        assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
+                .getTimeoutMs(
+                        preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
+                                + 1))
+                .isEqualTo(preferences.getSecretHandshakeLongTimeoutMs());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void getTimeout_retryNotEnabled_getOrigin() {
+        Preferences preferences = Preferences.builder().build();
+
+        assertThat(
+                new HandshakeHandler(
+                        createGattConnectionManager(Preferences.builder(), () -> {}),
+                        PROVIDER_BLE_ADDRESS,
+                        Preferences.builder()
+                                .setRetryGattConnectionAndSecretHandshake(false).build(),
+                        mEventLoggerWrapper,
+                        /* fastPairSignalChecker= */ null)
+                        .getTimeoutMs(0))
+                .isEqualTo(Duration.ofSeconds(
+                        preferences.getGattOperationTimeoutSeconds()).toMillis());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void triggersActionOverBle_notCrash() {
+        HandshakeHandler.ActionOverBle.Builder actionOverBleBuilder =
+                new HandshakeHandler.ActionOverBle.Builder()
+                        .addFlag(
+                                Constants.FastPairService.KeyBasedPairingCharacteristic
+                                        .ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC)
+                        .setVerificationData(BluetoothAddress.decode(RANDOM_PRIVATE_ADDRESS))
+                        .setAdditionalDataType(AdditionalDataType.PERSONALIZED_NAME)
+                        .setEvent(0, 0)
+                        .setEventAdditionalData(new byte[]{1})
+                        .getThis();
+        HandshakeHandler.ActionOverBle actionOverBle = actionOverBleBuilder.build();
+        assertThat(actionOverBle.getBytes().length).isEqualTo(16);
+        assertThat(
+                Arrays.equals(
+                        Arrays.copyOfRange(actionOverBle.getBytes(), 0, 12),
+                        new byte[]{
+                                (byte) 16, (byte)  -64, (byte) -69, (byte) -69,
+                                (byte) -69, (byte)  -69, (byte) -69, (byte) 30,
+                                (byte) 0, (byte) 0, (byte) 1, (byte) 1}))
+                .isTrue();
+    }
+
+    private GattConnectionManager createGattConnectionManager(
+            Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) {
+        return new GattConnectionManager(
+                ApplicationProvider.getApplicationContext(),
+                prefs.build(),
+                new EventLoggerWrapper(null),
+                BluetoothAdapter.getDefaultAdapter(),
+                toggleBluetooth,
+                PROVIDER_BLE_ADDRESS,
+                new TimingLogger("GattConnectionManager", prefs.build()),
+                /* fastPairSignalChecker= */ null,
+                /* setMtu= */ false);
+    }
+
+    private HandshakeHandler getHandshakeHandler(
+            GattConnectionManager gattConnectionManager,
+            @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
+        return new HandshakeHandler(
+                gattConnectionManager,
+                PROVIDER_BLE_ADDRESS,
+                Preferences.builder()
+                        .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257))
+                        .setRetrySecretHandshakeTimeout(true)
+                        .build(),
+                mEventLoggerWrapper,
+                fastPairSignalChecker);
+    }
+
+    private HandshakeHandler getHandshakeHandler(
+            boolean getEnable128BitCustomGattCharacteristicsId) {
+        return new HandshakeHandler(
+                createGattConnectionManager(Preferences.builder(), () -> {}),
+                PROVIDER_BLE_ADDRESS,
+                Preferences.builder()
+                        .setGattOperationTimeoutSeconds(5)
+                        .setEnable128BitCustomGattCharacteristicsId(
+                                getEnable128BitCustomGattCharacteristicsId)
+                        .build(),
+                mEventLoggerWrapper,
+                /* fastPairSignalChecker= */ null);
+    }
+
+    private static class HandshakeRequest {
+
+        /**
+         * 16 bytes data: 1-byte for type, 1-byte for flags, 6-bytes for provider's BLE address, 8
+         * bytes optional data.
+         *
+         * @see {go/fast-pair-spec-handshake-message1}
+         */
+        private final byte[] mDecryptedMessage;
+
+        HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)
+                throws GeneralSecurityException {
+            mDecryptedMessage = AesEcbSingleBlockEncryption.decrypt(key, encryptedPairingRequest);
+        }
+
+        /**
+         * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based
+         * Pairing Request and 0x10 for Action Request.
+         */
+        public Type getType() {
+            return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]);
+        }
+
+        /**
+         * Gets verification data of this handshake request.
+         * Currently, we use Provider's BLE address.
+         */
+        public byte[] getVerificationData() {
+            return Arrays.copyOfRange(
+                    mDecryptedMessage,
+                    Request.VERIFICATION_DATA_INDEX,
+                    Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH);
+        }
+
+        /** Gets Seeker's public address of the handshake request. */
+        public byte[] getSeekerPublicAddress() {
+            return Arrays.copyOfRange(
+                    mDecryptedMessage,
+                    Request.SEEKER_PUBLIC_ADDRESS_INDEX,
+                    Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH);
+        }
+
+        /** Checks whether the Seeker request discoverability from flags byte. */
+        public boolean requestDiscoverable() {
+            return (getFlags() & REQUEST_DISCOVERABLE) != 0;
+        }
+
+        /**
+         * Checks whether the Seeker requests that the Provider shall initiate bonding from
+         * flags byte.
+         */
+        public boolean requestProviderInitialBonding() {
+            return (getFlags() & PROVIDER_INITIATES_BONDING) != 0;
+        }
+
+        /** Checks whether the Seeker requests that the Provider shall notify the existing name. */
+        public boolean requestDeviceName() {
+            return (getFlags() & REQUEST_DEVICE_NAME) != 0;
+        }
+
+        /** Checks whether this is for retroactively writing account key. */
+        public boolean requestRetroactivePair() {
+            return (getFlags() & REQUEST_RETROACTIVE_PAIR) != 0;
+        }
+
+        /** Gets the flags of this handshake request. */
+        private byte getFlags() {
+            return mDecryptedMessage[Request.FLAGS_INDEX];
+        }
+
+        /** Checks whether the Seeker requests a device action. */
+        public boolean requestDeviceAction() {
+            return (getFlags() & DEVICE_ACTION) != 0;
+        }
+
+        /**
+         * Checks whether the Seeker requests an action which will be followed by an additional
+         * data.
+         */
+        public boolean requestFollowedByAdditionalData() {
+            return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0;
+        }
+
+        /** Gets the {@link AdditionalDataType} of this handshake request. */
+        @AdditionalDataType
+        public int getAdditionalDataType() {
+            if (!requestFollowedByAdditionalData()
+                    || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
+                return AdditionalDataType.UNKNOWN;
+            }
+            return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
+        }
+
+        /** Enumerates the handshake message types. */
+        public enum Type {
+            KEY_BASED_PAIRING_REQUEST(Request.TYPE_KEY_BASED_PAIRING_REQUEST),
+            ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE),
+            UNKNOWN((byte) 0xFF);
+
+            private final byte mValue;
+
+            Type(byte type) {
+                mValue = type;
+            }
+
+            public static Type valueOf(byte value) {
+                for (Type type : Type.values()) {
+                    if (type.getValue() == value) {
+                        return type;
+                    }
+                }
+                return UNKNOWN;
+            }
+
+            public byte getValue() {
+                return mValue;
+            }
+        }
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
index 670b2ca..32e62a3 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
@@ -196,5 +196,63 @@
 
         assertThat(headsetPiece.isBatteryLow()).isFalse();
     }
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void describeContents() {
+        HeadsetPiece headsetPiece =
+                HeadsetPiece.builder()
+                        .setLowLevelThreshold(30)
+                        .setBatteryLevel(18)
+                        .setImageUrl("http://fake.image.path/image.png")
+                        .setCharging(true)
+                        .build();
+        assertThat(headsetPiece.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void hashcode() {
+        HeadsetPiece headsetPiece =
+                HeadsetPiece.builder()
+                        .setLowLevelThreshold(30)
+                        .setBatteryLevel(18)
+                        .setImageUrl("http://fake.image.path/image.png")
+                        .setCharging(true)
+                        .build();
+        HeadsetPiece headsetPiece1 =
+                HeadsetPiece.builder()
+                        .setLowLevelThreshold(30)
+                        .setBatteryLevel(18)
+                        .setImageUrl("http://fake.image.path/image.png")
+                        .setCharging(true)
+                        .build();
+        assertThat(headsetPiece.hashCode()).isEqualTo(headsetPiece1.hashCode());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testToString() {
+        HeadsetPiece headsetPiece =
+                HeadsetPiece.builder()
+                        .setLowLevelThreshold(30)
+                        .setBatteryLevel(18)
+                        .setImageUrl("http://fake.image.path/image.png")
+                        .setCharging(true)
+                        .build();
+        assertThat(headsetPiece.toString())
+                .isEqualTo("HeadsetPiece{lowLevelThreshold=30,"
+                        + " batteryLevel=18,"
+                        + " imageUrl=http://fake.image.path/image.png,"
+                        + " charging=true,"
+                        + " imageContentUri=null}");
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreatorNewArray() {
+        HeadsetPiece[]  headsetPieces =
+                HeadsetPiece.CREATOR.newArray(2);
+        assertThat(headsetPieces.length).isEqualTo(2);
+    }
 }
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java
new file mode 100644
index 0000000..81a5d92
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link Ltv}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LtvTest {
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testParseEmpty_throwsException() throws Ltv.ParseException {
+        assertThrows(Ltv.ParseException.class,
+                () -> Ltv.parse(new byte[]{0}));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testParse_finishesSuccessfully() throws Ltv.ParseException {
+        assertThat(Ltv.parse(new byte[]{3, 4, 5, 6})).isNotEmpty();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java
new file mode 100644
index 0000000..a58a92d
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class PairingProgressListenerTest {
+
+    @Test
+    public void testFromOrdinal() {
+        assertThat(PairingProgressListener.fromOrdinal(0)).isEqualTo(
+                PairingProgressListener.PairingEvent.START);
+        assertThat(PairingProgressListener.fromOrdinal(1)).isEqualTo(
+                PairingProgressListener.PairingEvent.SUCCESS);
+        assertThat(PairingProgressListener.fromOrdinal(2)).isEqualTo(
+                PairingProgressListener.PairingEvent.FAILED);
+        assertThat(PairingProgressListener.fromOrdinal(3)).isEqualTo(
+                PairingProgressListener.PairingEvent.UNKNOWN);
+        assertThat(PairingProgressListener.fromOrdinal(4)).isEqualTo(
+                PairingProgressListener.PairingEvent.UNKNOWN);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
index b40a5a5..378e3b9 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
@@ -1266,12 +1266,38 @@
     public void testExtraLoggingInformation() {
         Preferences prefs =
                 Preferences.builder().setExtraLoggingInformation(FIRST_EXTRA_LOGGING_INFO).build();
-        assertThat(prefs.getExtraLoggingInformation()).isEqualTo(FIRST_EXTRA_LOGGING_INFO);
-        assertThat(prefs.toBuilder().build().getExtraLoggingInformation())
-                .isEqualTo(FIRST_EXTRA_LOGGING_INFO);
-
         Preferences prefs2 =
                 Preferences.builder().setExtraLoggingInformation(SECOND_EXTRA_LOGGING_INFO).build();
+        Preferences prefs3 =
+                Preferences.builder().setExtraLoggingInformation(FIRST_EXTRA_LOGGING_INFO).build();
+
+        assertThat(prefs.getExtraLoggingInformation()).isEqualTo(FIRST_EXTRA_LOGGING_INFO);
         assertThat(prefs2.getExtraLoggingInformation()).isEqualTo(SECOND_EXTRA_LOGGING_INFO);
+        assertThat(prefs.toBuilder().build().getExtraLoggingInformation())
+                .isEqualTo(FIRST_EXTRA_LOGGING_INFO);
+        // Test equal()
+        assertThat(prefs.getExtraLoggingInformation().equals(null)).isFalse();
+        assertThat(prefs.getExtraLoggingInformation()
+                .equals(prefs2.getExtraLoggingInformation())).isFalse();
+        assertThat(prefs.getExtraLoggingInformation()
+                .equals(prefs3.getExtraLoggingInformation())).isTrue();
+        // Test getModelId()
+        assertThat(prefs.getExtraLoggingInformation().getModelId())
+                .isEqualTo(prefs3.getExtraLoggingInformation().getModelId());
+        assertThat(prefs.getExtraLoggingInformation().getModelId())
+                .isNotEqualTo(prefs2.getExtraLoggingInformation().getModelId());
+        // Test hashCode()
+        assertThat(prefs.getExtraLoggingInformation().hashCode())
+                .isEqualTo(prefs3.getExtraLoggingInformation().hashCode());
+        assertThat(prefs.getExtraLoggingInformation().hashCode())
+                .isNotEqualTo(prefs2.getExtraLoggingInformation().hashCode());
+        // Test toBuilder()
+
+        assertThat(prefs.getExtraLoggingInformation()
+                .toBuilder().setModelId("000007").build().getModelId())
+                .isEqualTo("000007");
+        // Test toString()
+        assertThat(prefs.getExtraLoggingInformation().toString())
+                .isEqualTo("ExtraLoggingInformation{modelId=000006}");
     }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java
new file mode 100644
index 0000000..e8b9480
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.google.common.truth.Truth.assertThat;
+
+import com.android.server.nearby.intdefs.FastPairEventIntDefs;
+
+import org.junit.Test;
+
+public class TdsExceptionTest {
+    @Test
+    public void testGetErrorCode() {
+        TdsException tdsException = new TdsException(
+                FastPairEventIntDefs.BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID, "format");
+        assertThat(tdsException.getErrorCode())
+                .isEqualTo(FastPairEventIntDefs.BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID);
+
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java
new file mode 100644
index 0000000..a9ab7da
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothAdapter}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAdapterTest {
+
+    private static final byte[] BYTES = new byte[]{0, 1, 2, 3, 4, 5};
+    private static final String ADDRESS = "00:11:22:33:AA:BB";
+
+    @Mock private android.bluetooth.BluetoothAdapter mBluetoothAdapter;
+    @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+    @Mock private android.bluetooth.le.BluetoothLeAdvertiser mBluetoothLeAdvertiser;
+    @Mock private android.bluetooth.le.BluetoothLeScanner mBluetoothLeScanner;
+
+    BluetoothAdapter mTestabilityBluetoothAdapter;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTestabilityBluetoothAdapter = BluetoothAdapter.wrap(mBluetoothAdapter);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNullAdapter_isNull() {
+        assertThat(BluetoothAdapter.wrap(null)).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mTestabilityBluetoothAdapter).isNotNull();
+        assertThat(mTestabilityBluetoothAdapter.unwrap()).isSameInstanceAs(mBluetoothAdapter);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDisable_callsWrapped() {
+        when(mBluetoothAdapter.disable()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.disable()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnable_callsWrapped() {
+        when(mBluetoothAdapter.enable()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.enable()).isTrue();
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.isEnabled()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBluetoothLeAdvertiser_callsWrapped() {
+        when(mBluetoothAdapter.getBluetoothLeAdvertiser()).thenReturn(mBluetoothLeAdvertiser);
+        assertThat(mTestabilityBluetoothAdapter.getBluetoothLeAdvertiser().unwrap())
+                .isSameInstanceAs(mBluetoothLeAdvertiser);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBluetoothLeScanner_callsWrapped() {
+        when(mBluetoothAdapter.getBluetoothLeScanner()).thenReturn(mBluetoothLeScanner);
+        assertThat(mTestabilityBluetoothAdapter.getBluetoothLeScanner().unwrap())
+                .isSameInstanceAs(mBluetoothLeScanner);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBondedDevices_callsWrapped() {
+        when(mBluetoothAdapter.getBondedDevices()).thenReturn(null);
+        assertThat(mTestabilityBluetoothAdapter.getBondedDevices()).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsDiscovering_pcallsWrapped() {
+        when(mBluetoothAdapter.isDiscovering()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.isDiscovering()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartDiscovery_callsWrapped() {
+        when(mBluetoothAdapter.startDiscovery()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.startDiscovery()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCancelDiscovery_callsWrapped() {
+        when(mBluetoothAdapter.cancelDiscovery()).thenReturn(true);
+        assertThat(mTestabilityBluetoothAdapter.cancelDiscovery()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetRemoteDeviceBytes_callsWrapped() {
+        when(mBluetoothAdapter.getRemoteDevice(BYTES)).thenReturn(mBluetoothDevice);
+        assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(BYTES).unwrap())
+                .isSameInstanceAs(mBluetoothDevice);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetRemoteDeviceString_callsWrapped() {
+        when(mBluetoothAdapter.getRemoteDevice(ADDRESS)).thenReturn(mBluetoothDevice);
+        assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(ADDRESS).unwrap())
+                .isSameInstanceAs(mBluetoothDevice);
+
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java
new file mode 100644
index 0000000..d494024
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothDevice}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothDeviceTest {
+    private static final UUID UUID_CONST = UUID.randomUUID();
+    private static final String ADDRESS = "ADDRESS";
+    private static final String STRING = "STRING";
+
+    @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+    @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+    @Mock private android.bluetooth.BluetoothSocket mBluetoothSocket;
+    @Mock private android.bluetooth.BluetoothClass mBluetoothClass;
+
+    BluetoothDevice mTestabilityBluetoothDevice;
+    BluetoothGattCallback mTestBluetoothGattCallback;
+    Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTestabilityBluetoothDevice = BluetoothDevice.wrap(mBluetoothDevice);
+        mTestBluetoothGattCallback = new TestBluetoothGattCallback();
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mTestabilityBluetoothDevice).isNotNull();
+        assertThat(mTestabilityBluetoothDevice.unwrap()).isSameInstanceAs(mBluetoothDevice);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEquality_asExpected() {
+        assertThat(mTestabilityBluetoothDevice.equals(null)).isFalse();
+        assertThat(mTestabilityBluetoothDevice.equals(mTestabilityBluetoothDevice)).isTrue();
+        assertThat(mTestabilityBluetoothDevice.equals(BluetoothDevice.wrap(mBluetoothDevice)))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHashCode_asExpected() {
+        assertThat(mTestabilityBluetoothDevice.hashCode())
+                .isEqualTo(BluetoothDevice.wrap(mBluetoothDevice).hashCode());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConnectGattWithThreeParameters_callsWrapped() {
+        when(mBluetoothDevice
+                .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap()))
+                .thenReturn(mBluetoothGatt);
+        assertThat(mTestabilityBluetoothDevice
+                .connectGatt(mContext, true, mTestBluetoothGattCallback)
+                .unwrap())
+                .isSameInstanceAs(mBluetoothGatt);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConnectGattWithFourParameters_callsWrapped() {
+        when(mBluetoothDevice
+                .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap(), 1))
+                .thenReturn(mBluetoothGatt);
+        assertThat(mTestabilityBluetoothDevice
+                .connectGatt(mContext, true, mTestBluetoothGattCallback, 1)
+                .unwrap())
+                .isSameInstanceAs(mBluetoothGatt);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreateRfcommSocketToServiceRecord_callsWrapped() throws IOException {
+        when(mBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST))
+                .thenReturn(mBluetoothSocket);
+        assertThat(mTestabilityBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST))
+                .isSameInstanceAs(mBluetoothSocket);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreateInsecureRfcommSocketToServiceRecord_callsWrapped() throws IOException {
+        when(mBluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID_CONST))
+                .thenReturn(mBluetoothSocket);
+        assertThat(mTestabilityBluetoothDevice
+                .createInsecureRfcommSocketToServiceRecord(UUID_CONST))
+                .isSameInstanceAs(mBluetoothSocket);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetPairingConfirmation_callsWrapped() throws IOException {
+        when(mBluetoothDevice.setPairingConfirmation(true)).thenReturn(true);
+        assertThat(mTestabilityBluetoothDevice.setPairingConfirmation(true)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testFetchUuidsWithSdp_callsWrapped() throws IOException {
+        when(mBluetoothDevice.fetchUuidsWithSdp()).thenReturn(true);
+        assertThat(mTestabilityBluetoothDevice.fetchUuidsWithSdp()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreateBond_callsWrapped() throws IOException {
+        when(mBluetoothDevice.createBond()).thenReturn(true);
+        assertThat(mTestabilityBluetoothDevice.createBond()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetUuids_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getUuids()).thenReturn(null);
+        assertThat(mTestabilityBluetoothDevice.getUuids()).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBondState_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getBondState()).thenReturn(1);
+        assertThat(mTestabilityBluetoothDevice.getBondState()).isEqualTo(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetAddress_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getAddress()).thenReturn(ADDRESS);
+        assertThat(mTestabilityBluetoothDevice.getAddress()).isSameInstanceAs(ADDRESS);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetBluetoothClass_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getBluetoothClass()).thenReturn(mBluetoothClass);
+        assertThat(mTestabilityBluetoothDevice.getBluetoothClass())
+                .isSameInstanceAs(mBluetoothClass);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetType_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getType()).thenReturn(1);
+        assertThat(mTestabilityBluetoothDevice.getType()).isEqualTo(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetName_callsWrapped() throws IOException {
+        when(mBluetoothDevice.getName()).thenReturn(STRING);
+        assertThat(mTestabilityBluetoothDevice.getName()).isSameInstanceAs(STRING);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testToString_callsWrapped() {
+        when(mBluetoothDevice.toString()).thenReturn(STRING);
+        assertThat(mTestabilityBluetoothDevice.toString()).isSameInstanceAs(STRING);
+    }
+
+    private static class TestBluetoothGattCallback extends BluetoothGattCallback {}
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java
new file mode 100644
index 0000000..26ae6d7
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link BluetoothGattCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattCallbackTest {
+    @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+    @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+    @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+    TestBluetoothGattCallback mTestBluetoothGattCallback = new TestBluetoothGattCallback();
+
+    @Test
+    public void testOnConnectionStateChange_notCrash() {
+        mTestBluetoothGattCallback.unwrap()
+                .onConnectionStateChange(mBluetoothGatt, 1, 1);
+    }
+
+    @Test
+    public void testOnServiceDiscovered_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onServicesDiscovered(mBluetoothGatt, 1);
+    }
+
+    @Test
+    public void testOnCharacteristicRead_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onCharacteristicRead(mBluetoothGatt,
+                mBluetoothGattCharacteristic, 1);
+    }
+
+    @Test
+    public void testOnCharacteristicWrite_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onCharacteristicWrite(mBluetoothGatt,
+                mBluetoothGattCharacteristic, 1);
+    }
+
+    @Test
+    public void testOnDescriptionRead_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onDescriptorRead(mBluetoothGatt,
+                mBluetoothGattDescriptor, 1);
+    }
+
+    @Test
+    public void testOnDescriptionWrite_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onDescriptorWrite(mBluetoothGatt,
+                mBluetoothGattDescriptor, 1);
+    }
+
+    @Test
+    public void testOnReadRemoteRssi_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onReadRemoteRssi(mBluetoothGatt, 1, 1);
+    }
+
+    @Test
+    public void testOnReliableWriteCompleted_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onReliableWriteCompleted(mBluetoothGatt, 1);
+    }
+
+    @Test
+    public void testOnMtuChanged_notCrash() {
+        mTestBluetoothGattCallback.unwrap().onMtuChanged(mBluetoothGatt, 1, 1);
+    }
+
+    @Test
+    public void testOnCharacteristicChanged_notCrash() {
+        mTestBluetoothGattCallback.unwrap()
+                .onCharacteristicChanged(mBluetoothGatt, mBluetoothGattCharacteristic);
+    }
+
+    private static class TestBluetoothGattCallback extends BluetoothGattCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java
new file mode 100644
index 0000000..fb99317
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link BluetoothGattServerCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattServerCallbackTest {
+    @Mock
+    private android.bluetooth.BluetoothDevice mBluetoothDevice;
+    @Mock
+    private android.bluetooth.BluetoothGattService mBluetoothGattService;
+    @Mock
+    private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+    @Mock
+    private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+    TestBluetoothGattServerCallback
+            mTestBluetoothGattServerCallback = new TestBluetoothGattServerCallback();
+
+    @Test
+    public void testOnCharacteristicReadRequest_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onCharacteristicReadRequest(
+                mBluetoothDevice, 1, 1, mBluetoothGattCharacteristic);
+    }
+
+    @Test
+    public void testOnCharacteristicWriteRequest_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onCharacteristicWriteRequest(
+                mBluetoothDevice,
+                1,
+                mBluetoothGattCharacteristic,
+                false,
+                true,
+                1,
+                new byte[]{1});
+    }
+
+    @Test
+    public void testOnConnectionStateChange_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onConnectionStateChange(
+                mBluetoothDevice,
+                1,
+                2);
+    }
+
+    @Test
+    public void testOnDescriptorReadRequest_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onDescriptorReadRequest(
+                mBluetoothDevice,
+                1,
+                2, mBluetoothGattDescriptor);
+    }
+
+    @Test
+    public void testOnDescriptorWriteRequest_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onDescriptorWriteRequest(
+                mBluetoothDevice,
+                1,
+                mBluetoothGattDescriptor,
+                false,
+                true,
+                2,
+                new byte[]{1});
+    }
+
+    @Test
+    public void testOnExecuteWrite_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onExecuteWrite(
+                mBluetoothDevice,
+                1,
+                false);
+    }
+
+    @Test
+    public void testOnMtuChanged_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onMtuChanged(
+                mBluetoothDevice,
+                1);
+    }
+
+    @Test
+    public void testOnNotificationSent_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onNotificationSent(
+                mBluetoothDevice,
+                1);
+    }
+
+    @Test
+    public void testOnServiceAdded_notCrash() {
+        mTestBluetoothGattServerCallback.unwrap().onServiceAdded(1, mBluetoothGattService);
+    }
+
+    private static class TestBluetoothGattServerCallback extends BluetoothGattServerCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java
new file mode 100644
index 0000000..48283d1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothGattServer}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattServerTest {
+    private static final UUID UUID_CONST = UUID.randomUUID();
+    private static final byte[] BYTES = new byte[]{1, 2, 3};
+
+    @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+    @Mock private android.bluetooth.BluetoothGattServer mBluetoothGattServer;
+    @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService;
+    @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+
+    BluetoothGattServer mTestabilityBluetoothGattServer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTestabilityBluetoothGattServer = BluetoothGattServer.wrap(mBluetoothGattServer);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mTestabilityBluetoothGattServer).isNotNull();
+        assertThat(mTestabilityBluetoothGattServer.unwrap()).isSameInstanceAs(mBluetoothGattServer);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConnect_callsWrapped() {
+        when(mBluetoothGattServer
+                .connect(mBluetoothDevice, true))
+                .thenReturn(true);
+        assertThat(mTestabilityBluetoothGattServer
+                .connect(BluetoothDevice.wrap(mBluetoothDevice), true))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAddService_callsWrapped() {
+        when(mBluetoothGattServer
+                .addService(mBluetoothGattService))
+                .thenReturn(true);
+        assertThat(mTestabilityBluetoothGattServer
+                .addService(mBluetoothGattService))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testClearServices_callsWrapped() {
+        doNothing().when(mBluetoothGattServer).clearServices();
+        mTestabilityBluetoothGattServer.clearServices();
+        verify(mBluetoothGattServer).clearServices();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testClose_callsWrapped() {
+        doNothing().when(mBluetoothGattServer).close();
+        mTestabilityBluetoothGattServer.close();
+        verify(mBluetoothGattServer).close();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNotifyCharacteristicChanged_callsWrapped() {
+        when(mBluetoothGattServer
+                .notifyCharacteristicChanged(
+                        mBluetoothDevice,
+                        mBluetoothGattCharacteristic,
+                        true))
+                .thenReturn(true);
+        assertThat(mTestabilityBluetoothGattServer
+                .notifyCharacteristicChanged(
+                        BluetoothDevice.wrap(mBluetoothDevice),
+                        mBluetoothGattCharacteristic,
+                        true))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSendResponse_callsWrapped() {
+        when(mBluetoothGattServer.sendResponse(
+                mBluetoothDevice, 1, 1, 1, BYTES)).thenReturn(true);
+        mTestabilityBluetoothGattServer.sendResponse(
+                BluetoothDevice.wrap(mBluetoothDevice), 1, 1, 1, BYTES);
+        verify(mBluetoothGattServer).sendResponse(
+                mBluetoothDevice, 1, 1, 1, BYTES);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCancelConnection_callsWrapped() {
+        doNothing().when(mBluetoothGattServer).cancelConnection(mBluetoothDevice);
+        mTestabilityBluetoothGattServer.cancelConnection(BluetoothDevice.wrap(mBluetoothDevice));
+        verify(mBluetoothGattServer).cancelConnection(mBluetoothDevice);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetService_callsWrapped() {
+        when(mBluetoothGattServer.getService(UUID_CONST)).thenReturn(null);
+        assertThat(mTestabilityBluetoothGattServer.getService(UUID_CONST)).isNull();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java
new file mode 100644
index 0000000..a03a255
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothGattWrapper}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattWrapperTest {
+    private static final UUID UUID_CONST = UUID.randomUUID();
+    private static final byte[] BYTES = new byte[]{1, 2, 3};
+
+    @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+    @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+    @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService;
+    @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+    @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+    BluetoothGattWrapper mBluetoothGattWrapper;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBluetoothGattWrapper = BluetoothGattWrapper.wrap(mBluetoothGatt);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mBluetoothGattWrapper).isNotNull();
+        assertThat(mBluetoothGattWrapper.unwrap()).isSameInstanceAs(mBluetoothGatt);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEquality_asExpected() {
+        assertThat(mBluetoothGattWrapper.equals(null)).isFalse();
+        assertThat(mBluetoothGattWrapper.equals(mBluetoothGattWrapper)).isTrue();
+        assertThat(mBluetoothGattWrapper.equals(BluetoothGattWrapper.wrap(mBluetoothGatt)))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetDevice_callsWrapped() {
+        when(mBluetoothGatt.getDevice()).thenReturn(mBluetoothDevice);
+        assertThat(mBluetoothGattWrapper.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice);
+    }
+
+    @Test
+    public void testHashCode_asExpected() {
+        assertThat(mBluetoothGattWrapper.hashCode())
+                .isEqualTo(BluetoothGattWrapper.wrap(mBluetoothGatt).hashCode());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetServices_callsWrapped() {
+        when(mBluetoothGatt.getServices()).thenReturn(null);
+        assertThat(mBluetoothGattWrapper.getServices()).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGetService_callsWrapped() {
+        when(mBluetoothGatt.getService(UUID_CONST)).thenReturn(mBluetoothGattService);
+        assertThat(mBluetoothGattWrapper.getService(UUID_CONST))
+                .isSameInstanceAs(mBluetoothGattService);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDiscoverServices_callsWrapped() {
+        when(mBluetoothGatt.discoverServices()).thenReturn(true);
+        assertThat(mBluetoothGattWrapper.discoverServices()).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReadCharacteristic_callsWrapped() {
+        when(mBluetoothGatt.readCharacteristic(mBluetoothGattCharacteristic)).thenReturn(true);
+        assertThat(mBluetoothGattWrapper.readCharacteristic(mBluetoothGattCharacteristic)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteCharacteristic_callsWrapped() {
+        when(mBluetoothGatt.writeCharacteristic(mBluetoothGattCharacteristic, BYTES, 1))
+                .thenReturn(1);
+        assertThat(mBluetoothGattWrapper.writeCharacteristic(
+                mBluetoothGattCharacteristic, BYTES, 1)).isEqualTo(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReadDescriptor_callsWrapped() {
+        when(mBluetoothGatt.readDescriptor(mBluetoothGattDescriptor)).thenReturn(false);
+        assertThat(mBluetoothGattWrapper.readDescriptor(mBluetoothGattDescriptor)).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteDescriptor_callsWrapped() {
+        when(mBluetoothGatt.writeDescriptor(mBluetoothGattDescriptor, BYTES)).thenReturn(5);
+        assertThat(mBluetoothGattWrapper.writeDescriptor(mBluetoothGattDescriptor, BYTES))
+                .isEqualTo(5);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReadRemoteRssi_callsWrapped() {
+        when(mBluetoothGatt.readRemoteRssi()).thenReturn(false);
+        assertThat(mBluetoothGattWrapper.readRemoteRssi()).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRequestConnectionPriority_callsWrapped() {
+        when(mBluetoothGatt.requestConnectionPriority(5)).thenReturn(false);
+        assertThat(mBluetoothGattWrapper.requestConnectionPriority(5)).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRequestMtu_callsWrapped() {
+        when(mBluetoothGatt.requestMtu(5)).thenReturn(false);
+        assertThat(mBluetoothGattWrapper.requestMtu(5)).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSetCharacteristicNotification_callsWrapped() {
+        when(mBluetoothGatt.setCharacteristicNotification(mBluetoothGattCharacteristic, true))
+                .thenReturn(false);
+        assertThat(mBluetoothGattWrapper
+                .setCharacteristicNotification(mBluetoothGattCharacteristic, true)).isFalse();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDisconnect_callsWrapped() {
+        doNothing().when(mBluetoothGatt).disconnect();
+        mBluetoothGattWrapper.disconnect();
+        verify(mBluetoothGatt).disconnect();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testClose_callsWrapped() {
+        doNothing().when(mBluetoothGatt).close();
+        mBluetoothGattWrapper.close();
+        verify(mBluetoothGatt).close();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java
new file mode 100644
index 0000000..8468ed1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothLeAdvertiser}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAdvertiserTest {
+    @Mock android.bluetooth.le.BluetoothLeAdvertiser mWrappedBluetoothLeAdvertiser;
+    @Mock AdvertiseSettings mAdvertiseSettings;
+    @Mock AdvertiseData mAdvertiseData;
+    @Mock AdvertiseCallback mAdvertiseCallback;
+
+    BluetoothLeAdvertiser mBluetoothLeAdvertiser;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBluetoothLeAdvertiser = BluetoothLeAdvertiser.wrap(mWrappedBluetoothLeAdvertiser);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNullAdapter_isNull() {
+        assertThat(BluetoothLeAdvertiser.wrap(null)).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mWrappedBluetoothLeAdvertiser).isNotNull();
+        assertThat(mBluetoothLeAdvertiser.unwrap()).isSameInstanceAs(mWrappedBluetoothLeAdvertiser);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartAdvertisingThreeParameters_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeAdvertiser)
+                .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+        mBluetoothLeAdvertiser
+                .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+        verify(mWrappedBluetoothLeAdvertiser).startAdvertising(
+                mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartAdvertisingFourParameters_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeAdvertiser).startAdvertising(
+                mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+        mBluetoothLeAdvertiser.startAdvertising(
+                mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+        verify(mWrappedBluetoothLeAdvertiser).startAdvertising(
+                mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStopAdvertising_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback);
+        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
+        verify(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java
new file mode 100644
index 0000000..3fce54f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothLeScanner}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothLeScannerTest {
+    @Mock android.bluetooth.le.BluetoothLeScanner mWrappedBluetoothLeScanner;
+    @Mock PendingIntent mPendingIntent;
+    @Mock ScanSettings mScanSettings;
+    @Mock ScanFilter mScanFilter;
+
+    TestScanCallback mTestScanCallback = new TestScanCallback();
+    BluetoothLeScanner mBluetoothLeScanner;
+    ImmutableList<ScanFilter> mImmutableScanFilterList;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBluetoothLeScanner = BluetoothLeScanner.wrap(mWrappedBluetoothLeScanner);
+        mImmutableScanFilterList = ImmutableList.of(mScanFilter);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNullAdapter_isNull() {
+        assertThat(BluetoothLeAdvertiser.wrap(null)).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+        assertThat(mWrappedBluetoothLeScanner).isNotNull();
+        assertThat(mBluetoothLeScanner.unwrap()).isSameInstanceAs(mWrappedBluetoothLeScanner);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartScan_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap());
+        mBluetoothLeScanner.startScan(mTestScanCallback);
+        verify(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartScanWithFiltersCallback_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeScanner)
+                .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap());
+        mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback);
+        verify(mWrappedBluetoothLeScanner)
+                .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStartScanWithFiltersCallbackIntent_callsWrapped() {
+        when(mWrappedBluetoothLeScanner.startScan(
+                mImmutableScanFilterList, mScanSettings, mPendingIntent)).thenReturn(1);
+        mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent);
+        verify(mWrappedBluetoothLeScanner)
+                .startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStopScan_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap());
+        mBluetoothLeScanner.stopScan(mTestScanCallback);
+        verify(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testStopScanPendingIntent_callsWrapped() {
+        doNothing().when(mWrappedBluetoothLeScanner).stopScan(mPendingIntent);
+        mBluetoothLeScanner.stopScan(mPendingIntent);
+        verify(mWrappedBluetoothLeScanner).stopScan(mPendingIntent);
+    }
+
+    private static class TestScanCallback extends ScanCallback {};
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java
new file mode 100644
index 0000000..6d68486
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth.le;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ScanCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ScanCallbackTest {
+    @Mock android.bluetooth.le.ScanResult mScanResult;
+
+    TestScanCallback mTestScanCallback = new TestScanCallback();
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testOnScanFailed_notCrash() {
+        mTestScanCallback.unwrap().onScanFailed(1);
+    }
+
+    @Test
+    public void testOnScanResult_notCrash() {
+        mTestScanCallback.unwrap().onScanResult(1, mScanResult);
+    }
+
+    @Test
+    public void testOnBatchScanResult_notCrash() {
+        mTestScanCallback.unwrap().onBatchScanResults(ImmutableList.of(mScanResult));
+    }
+
+    private static class TestScanCallback extends ScanCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java
new file mode 100644
index 0000000..255c178
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ScanResult}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ScanResultTest {
+
+    @Mock android.bluetooth.le.ScanResult mWrappedScanResult;
+    @Mock android.bluetooth.le.ScanRecord mScanRecord;
+    @Mock android.bluetooth.BluetoothDevice mBluetoothDevice;
+    ScanResult mScanResult;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mScanResult = ScanResult.wrap(mWrappedScanResult);
+    }
+
+    @Test
+    public void testGetScanRecord_calledWrapped() {
+        when(mWrappedScanResult.getScanRecord()).thenReturn(mScanRecord);
+        assertThat(mScanResult.getScanRecord()).isSameInstanceAs(mScanRecord);
+    }
+
+    @Test
+    public void testGetRssi_calledWrapped() {
+        when(mWrappedScanResult.getRssi()).thenReturn(3);
+        assertThat(mScanResult.getRssi()).isEqualTo(3);
+    }
+
+    @Test
+    public void testGetTimestampNanos_calledWrapped() {
+        when(mWrappedScanResult.getTimestampNanos()).thenReturn(4L);
+        assertThat(mScanResult.getTimestampNanos()).isEqualTo(4L);
+    }
+
+    @Test
+    public void testGetDevice_calledWrapped() {
+        when(mWrappedScanResult.getDevice()).thenReturn(mBluetoothDevice);
+        assertThat(mScanResult.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
index 47182c3..7535c06 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
@@ -34,14 +34,12 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
-import java.util.UUID;
 
 /** Unit tests for {@link BluetoothGattUtils}. */
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BluetoothGattUtilsTest {
-    private static final UUID TEST_UUID = UUID.randomUUID();
     private static final ImmutableSet<String> GATT_HIDDEN_CONSTANTS = ImmutableSet.of(
             "GATT_WRITE_REQUEST_BUSY", "GATT_WRITE_REQUEST_FAIL", "GATT_WRITE_REQUEST_SUCCESS");
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
index 70dcec8..bebb2f2 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
@@ -36,7 +36,6 @@
     @Rule
     public ExpectedException thrown = ExpectedException.none();
 
-    /*
     @Test
     public void remove() {
         mEventLoop.postRunnable(new NumberedRunnable(0));
@@ -44,10 +43,8 @@
         mEventLoop.postRunnable(runnableToAddAndRemove);
         mEventLoop.removeRunnable(runnableToAddAndRemove);
         mEventLoop.postRunnable(new NumberedRunnable(2));
-
-        assertThat(mExecutedRunnables).containsExactly(0, 2);
+        assertThat(mExecutedRunnables).doesNotContain(1);
     }
-    */
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java
new file mode 100644
index 0000000..4775456
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.eventloop;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HandlerEventLoopImplTest {
+    private static final String TAG = "HandlerEventLoopImplTest";
+    private final HandlerEventLoopImpl mHandlerEventLoopImpl =
+            new HandlerEventLoopImpl(TAG);
+    private final List<Integer> mExecutedRunnables = new ArrayList<>();
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void remove() {
+        mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(0));
+        NumberedRunnable runnableToAddAndRemove = new NumberedRunnable(1);
+        mHandlerEventLoopImpl.postRunnable(runnableToAddAndRemove);
+        mHandlerEventLoopImpl.removeRunnable(runnableToAddAndRemove);
+        mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(2));
+        assertThat(mExecutedRunnables).doesNotContain(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void isPosted() {
+        NumberedRunnable runnable = new HandlerEventLoopImplTest.NumberedRunnable(0);
+        mHandlerEventLoopImpl.postRunnableDelayed(runnable, 10 * 1000L);
+        assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue();
+        mHandlerEventLoopImpl.removeRunnable(runnable);
+        assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isFalse();
+
+        // Let a runnable execute, then verify that it's not posted.
+        mHandlerEventLoopImpl.postRunnable(runnable);
+        assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void postAndWaitAfterDestroy() throws InterruptedException {
+        mHandlerEventLoopImpl.destroy();
+        mHandlerEventLoopImpl.postAndWait(new HandlerEventLoopImplTest.NumberedRunnable(0));
+        assertThat(mExecutedRunnables).isEmpty();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void postEmptyQueueRunnable() {
+        mHandlerEventLoopImpl.postEmptyQueueRunnable(
+                new HandlerEventLoopImplTest.NumberedRunnable(0));
+    }
+
+    private class NumberedRunnable extends NamedRunnable {
+        private final int mId;
+
+        private NumberedRunnable(int id) {
+            super("NumberedRunnable:" + id);
+            this.mId = id;
+        }
+
+        @Override
+        public void run() {
+            // Note: when running in robolectric, this is not actually executed on a different
+            // thread, it's executed in the same thread the test runs in, so this is safe.
+            mExecutedRunnables.add(mId);
+        }
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java
new file mode 100644
index 0000000..7005da1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.eventloop;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class NamedRunnableTest {
+    private static final String TAG = "NamedRunnableTest";
+
+    @Test
+    public void testToString() {
+        assertThat(mNamedRunnable.toString()).isEqualTo("Runnable[" + TAG +  "]");
+    }
+
+    private final NamedRunnable mNamedRunnable = new NamedRunnable(TAG) {
+        @Override
+        public void run() {
+        }
+    };
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java
new file mode 100644
index 0000000..d39d9cc
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class IconUtilsTest {
+    private static final int MIN_ICON_SIZE = 16;
+    private static final int DESIRED_ICON_SIZE = 32;
+    @Mock
+    Context mContext;
+
+    @Test
+    public void isIconSizedCorrectly() {
+        // Null bitmap is not sized correctly
+        assertThat(IconUtils.isIconSizeCorrect(null)).isFalse();
+
+        int minIconSize = MIN_ICON_SIZE;
+        int desiredIconSize = DESIRED_ICON_SIZE;
+
+        // Bitmap that is 1x1 pixels is not sized correctly
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        assertThat(IconUtils.isIconSizeCorrect(icon)).isFalse();
+
+        // Bitmap is categorized as small, and not regular
+        icon = Bitmap.createBitmap(minIconSize + 1,
+                minIconSize + 1, Bitmap.Config.ALPHA_8);
+        assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue();
+        assertThat(IconUtils.isIconSizedSmall(icon)).isTrue();
+        assertThat(IconUtils.isIconSizedRegular(icon)).isFalse();
+
+        // Bitmap is categorized as regular, but not small
+        icon = Bitmap.createBitmap(desiredIconSize + 1,
+                desiredIconSize + 1, Bitmap.Config.ALPHA_8);
+        assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue();
+        assertThat(IconUtils.isIconSizedSmall(icon)).isFalse();
+        assertThat(IconUtils.isIconSizedRegular(icon)).isTrue();
+    }
+
+    @Test
+    public void testAddWhiteCircleBackground() {
+        int minIconSize = MIN_ICON_SIZE;
+        Bitmap icon = Bitmap.createBitmap(minIconSize + 1, minIconSize + 1,
+                Bitmap.Config.ALPHA_8);
+
+        assertThat(
+                IconUtils.isIconSizeCorrect(IconUtils.addWhiteCircleBackground(mContext, icon)))
+                .isTrue();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java
new file mode 100644
index 0000000..c3a4e55
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.locator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.fastpair.FastPairAdvHandler;
+import com.android.server.nearby.fastpair.FastPairModule;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import src.com.android.server.nearby.fastpair.testing.MockingLocator;
+
+public class LocatorTest {
+    private Locator mLocator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLocator = src.com.android.server.nearby.fastpair.testing.MockingLocator.withMocksOnly(
+                ApplicationProvider.getApplicationContext());
+        mLocator.bind(new FastPairModule());
+    }
+
+    @Test
+    public void genericConstructor() {
+        assertThat(mLocator.get(FastPairCacheManager.class)).isNotNull();
+        assertThat(mLocator.get(FootprintsDeviceManager.class)).isNotNull();
+        assertThat(mLocator.get(EventLoop.class)).isNotNull();
+        assertThat(mLocator.get(FastPairHalfSheetManager.class)).isNotNull();
+        assertThat(mLocator.get(FastPairAdvHandler.class)).isNotNull();
+        assertThat(mLocator.get(Clock.class)).isNotNull();
+    }
+
+    @Test
+    public void genericDestroy() {
+        mLocator.destroy();
+    }
+
+    @Test
+    public void getOptional() {
+        assertThat(mLocator.getOptional(FastPairModule.class)).isNotNull();
+        mLocator.removeBindingForTest(FastPairModule.class);
+        assertThat(mLocator.getOptional(FastPairModule.class)).isNull();
+    }
+
+    @Test
+    public void getParent() {
+        assertThat(mLocator.getParent()).isNotNull();
+    }
+
+    @Test
+    public void getUnboundErrorMessage() {
+        assertThat(mLocator.getUnboundErrorMessage(FastPairModule.class))
+                .isEqualTo(
+                        "Unbound type: com.android.server.nearby.fastpair.FastPairModule\n"
+                        + "Searched locators:\n" + "android.app.Application ->\n"
+                                + "android.app.Application ->\n" + "android.app.Application");
+    }
+
+    @Test
+    public void getContextForTest() {
+        src.com.android.server.nearby.fastpair.testing.MockingLocator  mockingLocator =
+                new MockingLocator(ApplicationProvider.getApplicationContext(), mLocator);
+        assertThat(mockingLocator.getContextForTest()).isNotNull();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java
new file mode 100644
index 0000000..eafc7db
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.servicemonitor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+
+public class PackageWatcherTest {
+    private PackageWatcher mPackageWatcher = new PackageWatcher() {
+        @Override
+        public void onSomePackagesChanged() {
+        }
+    };
+
+    @Test
+    public void getPackageName() {
+        Intent intent = new Intent("Action", null);
+        assertThat(mPackageWatcher.getPackageName(intent)).isNull();
+    }
+
+    @Test
+    public void onReceive() {
+        Intent intent = new Intent(Intent.ACTION_PACKAGES_UNSUSPENDED, null);
+        mPackageWatcher.onReceive(ApplicationProvider.getApplicationContext(), intent);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
index 346a961..900b618 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
@@ -16,24 +16,39 @@
 
 package com.android.server.nearby.fastpair;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.accounts.Account;
 import android.content.Context;
 import android.nearby.FastPairDevice;
 
+import com.android.server.nearby.common.bloomfilter.BloomFilter;
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
 import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
 
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.time.Clock;
+import java.util.List;
+
+import service.proto.Cache;
+import service.proto.Data;
 import service.proto.Rpcs;
 
 public class FastPairAdvHandlerTest {
@@ -45,11 +60,39 @@
     private FastPairHalfSheetManager mFastPairHalfSheetManager;
     @Mock
     private FastPairNotificationManager mFastPairNotificationManager;
+    @Mock
+    private FastPairCacheManager mFastPairCacheManager;
+    @Mock
+    private FastPairController mFastPairController;
+    @Mock
+    private Data.FastPairDeviceWithAccountKey mFastPairDeviceWithAccountKey;
+    @Mock
+    private BloomFilter mBloomFilter;
+    @Mock
+    Cache.StoredFastPairItem mStoredFastPairItem;
+    @Mock private Clock mClock;
+
+    private final Account mAccount = new Account("test1@gmail.com", "com.google");
+    private static final byte[] ACCOUNT_KEY =
+            new byte[] {4, 65, 90, -26, -5, -38, -128, 40, -103, 101, 95, 55, 8, -42, -120, 78};
+    private static final byte[] ACCOUNT_KEY_2 = new byte[] {0, 1, 2};
     private static final String BLUETOOTH_ADDRESS = "AA:BB:CC:DD";
+    private static final String MODEL_ID = "MODEL_ID";
     private static final int CLOSE_RSSI = -80;
     private static final int FAR_AWAY_RSSI = -120;
     private static final int TX_POWER = -70;
     private static final byte[] INITIAL_BYTE_ARRAY = new byte[]{0x01, 0x02, 0x03};
+    private static final byte[] SUBSEQUENT_DATA_BYTES = new byte[]{
+            0, -112, -63, 32, 37, -20, 36, 0, -60, 0, -96, 17, -10, 51, -28, -28, 100};
+    private static final byte[] SUBSEQUENT_DATA_BYTES_INVALID = new byte[]{
+            0, -112, -63, 32, 37, -20, 48, 0, -60, 0, 90, 17, -10, 51, -28, -28, 100};
+    private static final byte[] SALT = new byte[]{0x01};
+    private static final Cache.StoredDiscoveryItem STORED_DISCOVERY_ITEM =
+            Cache.StoredDiscoveryItem.newBuilder()
+                    .setDeviceName("Device Name")
+                    .setTxPower(TX_POWER)
+                    .setMacAddress(BLUETOOTH_ADDRESS)
+                    .build();
 
     LocatorContextWrapper mLocatorContextWrapper;
     FastPairAdvHandler mFastPairAdvHandler;
@@ -65,8 +108,29 @@
         mLocatorContextWrapper.getLocator().overrideBindingForTest(
                 FastPairNotificationManager.class, mFastPairNotificationManager
         );
+        mLocatorContextWrapper.getLocator().overrideBindingForTest(
+                FastPairCacheManager.class, mFastPairCacheManager
+        );
+        mLocatorContextWrapper.getLocator().overrideBindingForTest(
+                FastPairController.class, mFastPairController);
+        mLocatorContextWrapper.getLocator().overrideBindingForTest(Clock.class, mClock);
+
         when(mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any()))
                 .thenReturn(Rpcs.GetObservedDeviceResponse.getDefaultInstance());
+        when(mFastPairDataProvider.loadFastPairEligibleAccounts()).thenReturn(List.of(mAccount));
+        when(mFastPairDataProvider.loadFastPairDeviceWithAccountKey(mAccount))
+                .thenReturn(List.of(mFastPairDeviceWithAccountKey));
+        when(mFastPairDataProvider.loadFastPairDeviceWithAccountKey(eq(mAccount), any()))
+                .thenReturn(List.of(mFastPairDeviceWithAccountKey));
+        when(mFastPairDeviceWithAccountKey.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY));
+        when(mFastPairDeviceWithAccountKey.getDiscoveryItem())
+                .thenReturn(STORED_DISCOVERY_ITEM);
+        when(mStoredFastPairItem.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY_2), ByteString.copyFrom(ACCOUNT_KEY_2));
+        when(mFastPairCacheManager.getAllSavedStoredFastPairItem())
+                .thenReturn(List.of(mStoredFastPairItem));
+
         mFastPairAdvHandler = new FastPairAdvHandler(mLocatorContextWrapper, mFastPairDataProvider);
     }
 
@@ -75,6 +139,7 @@
         FastPairDevice fastPairDevice = new FastPairDevice.Builder()
                 .setData(INITIAL_BYTE_ARRAY)
                 .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
                 .setRssi(CLOSE_RSSI)
                 .setTxPower(TX_POWER)
                 .build();
@@ -89,6 +154,7 @@
         FastPairDevice fastPairDevice = new FastPairDevice.Builder()
                 .setData(INITIAL_BYTE_ARRAY)
                 .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
                 .setRssi(FAR_AWAY_RSSI)
                 .setTxPower(TX_POWER)
                 .build();
@@ -99,37 +165,116 @@
     }
 
     @Test
-    public void testSubsequentBroadcast() {
-        byte[] fastPairRecordWithBloomFilter =
-                new byte[]{
-                        (byte) 0x02,
-                        (byte) 0x01,
-                        (byte) 0x02, // Flags
-                        (byte) 0x02,
-                        (byte) 0x0A,
-                        (byte) 0xEB, // Tx Power (-20)
-                        (byte) 0x0B,
-                        (byte) 0x16,
-                        (byte) 0x2C,
-                        (byte) 0xFE, // FastPair Service Data
-                        (byte) 0x00, // Flags (model ID length = 3)
-                        (byte) 0x40, // Account key hash flags (length = 4, type = 0)
-                        (byte) 0x11,
-                        (byte) 0x22,
-                        (byte) 0x33,
-                        (byte) 0x44, // Account key hash (0x11223344)
-                        (byte) 0x11, // Account key salt flags (length = 1, type = 1)
-                        (byte) 0x55, // Account key salt
-                };
+    public void testSubsequentBroadcast_showNotification() {
         FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .setData(fastPairRecordWithBloomFilter)
+                .setData(SUBSEQUENT_DATA_BYTES)
                 .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
                 .setRssi(CLOSE_RSSI)
                 .setTxPower(TX_POWER)
                 .build();
-
         mFastPairAdvHandler.handleBroadcast(fastPairDevice);
 
+        DiscoveryItem discoveryItem =
+                new DiscoveryItem(mLocatorContextWrapper, STORED_DISCOVERY_ITEM);
+        verify(mFastPairNotificationManager).showDiscoveryNotification(eq(discoveryItem),
+                eq(ACCOUNT_KEY));
         verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
     }
+
+    @Test
+    public void testSubsequentBroadcast_tooFar_notShowNotification() {
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(SUBSEQUENT_DATA_BYTES)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
+                .setRssi(FAR_AWAY_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairController, never()).pair(any(), any(), any());
+        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
+    }
+
+    @Test
+    public void testSubsequentBroadcast_notRecognize_notShowNotification() {
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(SUBSEQUENT_DATA_BYTES_INVALID)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
+                .setRssi(FAR_AWAY_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairController, never()).pair(any(), any(), any());
+        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
+    }
+
+    @Test
+    public void testSubsequentBroadcast_cached_notShowNotification() {
+        when(mStoredFastPairItem.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(SUBSEQUENT_DATA_BYTES_INVALID)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setModelId(MODEL_ID)
+                .setRssi(FAR_AWAY_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairController, never()).pair(any(), any(), any());
+        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
+    }
+
+    @Test
+    public void testFindRecognizedDevice_bloomFilterNotContains_notFound() {
+        when(mFastPairDeviceWithAccountKey.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+        when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false);
+
+        assertThat(FastPairAdvHandler.findRecognizedDevice(
+                ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNull();
+    }
+
+    @Test
+    public void testFindRecognizedDevice_bloomFilterContains_found() {
+        when(mFastPairDeviceWithAccountKey.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+        when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true);
+
+        assertThat(FastPairAdvHandler.findRecognizedDevice(
+                ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNotNull();
+    }
+
+    @Test
+    public void testFindRecognizedDeviceFromCachedItem_bloomFilterNotContains_notFound() {
+        when(mStoredFastPairItem.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+        when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false);
+
+        assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem(
+                ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNull();
+    }
+
+    @Test
+    public void testFindRecognizedDeviceFromCachedItem_bloomFilterContains_found() {
+        when(mStoredFastPairItem.getAccountKey())
+                .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+        when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true);
+
+        assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem(
+                ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNotNull();
+    }
+
+    @Test
+    public void testGenerateBatteryData_correct() {
+        byte[] data = new byte[]
+                {0, -112, 96, 5, -125, 45, 35, 98, 98, 81, 13, 17, 3, 51, -28, -28, -28};
+        assertThat(FastPairAdvHandler.generateBatteryData(data))
+                .isEqualTo(new byte[]{51, -28, -28, -28});
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
index 26d1847..00df1b9 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.nearby.fastpair;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -54,7 +55,7 @@
     public void testFastPairInit() {
         mFastPairManager.initiate();
 
-        verify(mContext, times(1)).registerReceiver(any(), any());
+        verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
     }
 
     @Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java
new file mode 100644
index 0000000..9cf65f4
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.fastpair;
+
+import org.junit.Test;
+
+public class FlagUtilsTest {
+
+    @Test
+    public void testGetPreferencesBuilder_notCrash() {
+        FlagUtils.getPreferencesBuilder().build();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java
new file mode 100644
index 0000000..5d4ea22
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2022 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.fastpair.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.FastPairManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+
+/** Unit tests for {@link DiscoveryItem} */
+public class DiscoveryItemTest {
+    private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final String DEFAULT_DESCRIPITON = "description";
+    private static final long DEFAULT_TIMESTAMP = 1000000000L;
+    private static final String DEFAULT_TITLE = "title";
+    private static final String APP_NAME = "app_name";
+    private static final String ACTION_URL =
+            "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;"
+            + "package=com.google.android.gms;"
+            + "component=com.google.android.gms/"
+            + ".nearby.discovery.service.DiscoveryService;end";
+    private static final String DISPLAY_URL = "DISPLAY_URL";
+    private static final String TRIGGER_ID = "trigger.id";
+    private static final String FAST_PAIR_ID = "id";
+    private static final int RSSI = -80;
+    private static final int TX_POWER = -10;
+
+    @Mock private Context mContext;
+    private LocatorContextWrapper mLocatorContextWrapper;
+    private FastPairCacheManager mFastPairCacheManager;
+    private FastPairManager mFastPairManager;
+    private DiscoveryItem mDiscoveryItem;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mLocatorContextWrapper = new LocatorContextWrapper(mContext);
+        mFastPairManager = new FastPairManager(mLocatorContextWrapper);
+        mFastPairCacheManager = mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class);
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+        mDiscoveryItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+    }
+
+    @Test
+    public void testMultipleFields() {
+        assertThat(mDiscoveryItem.getId()).isEqualTo(FAST_PAIR_ID);
+        assertThat(mDiscoveryItem.getDescription()).isEqualTo(DEFAULT_DESCRIPITON);
+        assertThat(mDiscoveryItem.getDisplayUrl()).isEqualTo(DISPLAY_URL);
+        assertThat(mDiscoveryItem.getTriggerId()).isEqualTo(TRIGGER_ID);
+        assertThat(mDiscoveryItem.getMacAddress()).isEqualTo(DEFAULT_MAC_ADDRESS);
+        assertThat(
+                mDiscoveryItem.getFirstObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP);
+        assertThat(
+                mDiscoveryItem.getLastObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP);
+        assertThat(mDiscoveryItem.getActionUrl()).isEqualTo(ACTION_URL);
+        assertThat(mDiscoveryItem.getAppName()).isEqualTo(APP_NAME);
+        assertThat(mDiscoveryItem.getRssi()).isEqualTo(RSSI);
+        assertThat(mDiscoveryItem.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(mDiscoveryItem.getFastPairInformation()).isNull();
+        assertThat(mDiscoveryItem.getFastPairSecretKey()).isNull();
+        assertThat(mDiscoveryItem.getIcon()).isNull();
+        assertThat(mDiscoveryItem.getIconFifeUrl()).isNotNull();
+        assertThat(mDiscoveryItem.getState()).isNotNull();
+        assertThat(mDiscoveryItem.getTitle()).isNotNull();
+        assertThat(mDiscoveryItem.isApp()).isFalse();
+        assertThat(mDiscoveryItem.isDeletable(
+                100000L, 0L)).isTrue();
+        assertThat(mDiscoveryItem.isDeviceType(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue();
+        assertThat(mDiscoveryItem.isExpired(
+                100000L, 0L)).isTrue();
+        assertThat(mDiscoveryItem.isFastPair()).isTrue();
+        assertThat(mDiscoveryItem.isPendingAppInstallValid(5)).isTrue();
+        assertThat(mDiscoveryItem.isPendingAppInstallValid(5,
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID,  null,
+                TRIGGER_ID,  DEFAULT_MAC_ADDRESS,  "", RSSI, TX_POWER))).isTrue();
+        assertThat(mDiscoveryItem.isTypeEnabled(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue();
+        assertThat(mDiscoveryItem.toString()).isNotNull();
+    }
+
+    @Test
+    public void isMuted() {
+        assertThat(mDiscoveryItem.isMuted()).isFalse();
+    }
+
+    @Test
+    public void itemWithDefaultDescription_shouldShowUp() {
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+        // Null description should not show up.
+        mDiscoveryItem.setStoredItemForTest(DiscoveryItem.newStoredDiscoveryItem());
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID,  null,
+                        TRIGGER_ID,  DEFAULT_MAC_ADDRESS,  "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+        // Empty description should not show up.
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID,  "",
+                        TRIGGER_ID,  DEFAULT_MAC_ADDRESS, DEFAULT_TITLE, RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+    }
+
+    @Test
+    public void itemWithEmptyTitle_shouldNotShowUp() {
+        // Null title should not show up.
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+        // Empty title should not show up.
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+        // Null title should not show up.
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, DEFAULT_MAC_ADDRESS, null, RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+    }
+
+    @Test
+    public void itemWithRssiAndTxPower_shouldHaveCorrectEstimatedDistance() {
+        assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(28.18);
+    }
+
+    @Test
+    public void itemWithoutRssiOrTxPower_shouldNotHaveEstimatedDistance() {
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", 0, 0));
+        assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(0);
+    }
+
+    @Test
+    public void getUiHashCode_differentAddress_differentHash() {
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+        DiscoveryItem compareTo =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        compareTo.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "55:44:33:22:11:00", "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.getUiHashCode()).isNotEqualTo(compareTo.getUiHashCode());
+    }
+
+    @Test
+    public void getUiHashCode_sameAddress_sameHash() {
+        mDiscoveryItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+        DiscoveryItem compareTo =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        compareTo.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.getUiHashCode()).isEqualTo(compareTo.getUiHashCode());
+    }
+
+    @Test
+    public void isFastPair() {
+        DiscoveryItem fastPairItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        assertThat(fastPairItem.isFastPair()).isTrue();
+    }
+
+    @Test
+    public void testEqual() {
+        DiscoveryItem fastPairItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+    }
+
+    @Test
+    public void testCompareTo() {
+        DiscoveryItem fastPairItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        assertThat(mDiscoveryItem.compareTo(fastPairItem)).isEqualTo(0);
+    }
+
+
+    @Test
+    public void testCopyOfStoredItem() {
+        DiscoveryItem fastPairItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        fastPairItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse();
+        fastPairItem.setStoredItemForTest(mDiscoveryItem.getCopyOfStoredItem());
+        assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+    }
+
+    @Test
+    public void testStoredItemForTest() {
+        DiscoveryItem fastPairItem =
+                FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+        fastPairItem.setStoredItemForTest(
+                FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+                        TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+        assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse();
+        fastPairItem.setStoredItemForTest(mDiscoveryItem.getStoredItemForTest());
+        assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
index fdda6f7..18f2cf6 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.le.ScanResult;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -50,6 +51,10 @@
     DiscoveryItem mDiscoveryItem2;
     @Mock
     Cache.StoredFastPairItem mStoredFastPairItem;
+    @Mock
+    ScanResult mScanResult;
+
+    Context mContext;
     Cache.StoredDiscoveryItem mStoredDiscoveryItem = Cache.StoredDiscoveryItem.newBuilder()
             .setTriggerId(MODEL_ID)
             .setAppName(APP_NAME).build();
@@ -60,12 +65,12 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mContext = ApplicationProvider.getApplicationContext();
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void notSaveRetrieveInfo() {
-        Context mContext = ApplicationProvider.getApplicationContext();
         when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
         when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
 
@@ -78,7 +83,6 @@
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void saveRetrieveInfo() {
-        Context mContext = ApplicationProvider.getApplicationContext();
         when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
         when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
         when(mDiscoveryItem2.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem2);
@@ -100,7 +104,6 @@
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void saveRetrieveInfoStoredFastPairItem() {
-        Context mContext = ApplicationProvider.getApplicationContext();
         Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
                 .setMacAddress(MAC_ADDRESS)
                 .setAccountKey(ACCOUNT_KEY)
@@ -118,7 +121,6 @@
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void checkGetAllFastPairItems() {
-        Context mContext = ApplicationProvider.getApplicationContext();
         Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
                 .setMacAddress(MAC_ADDRESS)
                 .setAccountKey(ACCOUNT_KEY)
@@ -139,5 +141,15 @@
 
         assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
                 .isEqualTo(1);
+
+        fastPairCacheManager.cleanUp();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void getDeviceFromScanResult_notCrash() {
+        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
+        fastPairCacheManager.getDeviceFromScanResult(mScanResult);
+
     }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java
new file mode 100644
index 0000000..c5428f5
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.fastpair.cache;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteException;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+public class FastPairDbHelperTest {
+
+    Context mContext;
+    FastPairDbHelper mFastPairDbHelper;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mFastPairDbHelper = new FastPairDbHelper(mContext);
+    }
+
+    @After
+    public void teardown() {
+        mFastPairDbHelper.close();
+    }
+
+    @Test
+    public void testUpgrade_notCrash() {
+        mFastPairDbHelper
+                .onUpgrade(mFastPairDbHelper.getWritableDatabase(), 1, 2);
+    }
+
+    @Test
+    public void testDowngrade_throwsException()  {
+        assertThrows(
+                SQLiteException.class,
+                () -> mFastPairDbHelper.onDowngrade(
+                        mFastPairDbHelper.getWritableDatabase(), 2, 1));
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java
new file mode 100644
index 0000000..f3afbe7
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2022 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.fastpair.halfsheet;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import com.android.server.nearby.fastpair.blocklist.Blocklist;
+import com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState;
+import com.android.server.nearby.fastpair.blocklist.BlocklistElement;
+import com.android.server.nearby.util.DefaultClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class FastPairHalfSheetBlocklistTest {
+
+    @Mock
+    private DefaultClock mClock;
+    private FastPairHalfSheetBlocklist mFastPairHalfSheetBlocklist;
+    private static final int SIZE_OF_BLOCKLIST = 2;
+    private static final long CURRENT_TIME = 1000000L;
+    private static final long BLOCKLIST_CANCEL_TIMEOUT_MILLIS = 30000L;
+    private static final long SUPPRESS_ALL_DURATION_MILLIS = 60000L;
+    private static final long DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND = 86400000;
+    private static final long STATE_EXPIRATION_MILLISECOND = 86400000;
+    private static final int HALFSHEET_ID = 1;
+    private static final long DURATION_MILLI_SECONDS_LONG = 86400000;
+    private static final int DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS = 1;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME);
+        mFastPairHalfSheetBlocklist = new FastPairHalfSheetBlocklist(SIZE_OF_BLOCKLIST, mClock);
+    }
+
+    @Test
+    public void testUpdateState() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME));
+
+        boolean initiallyBlocklisted =
+                mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                        DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS);
+
+        mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, Blocklist.BlocklistState.ACTIVE);
+        boolean isBlockListedWhenActive =
+                mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                        DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS);
+
+        mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, Blocklist.BlocklistState.DISMISSED);
+        boolean isBlockListedAfterDismissed =
+                mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                        DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS);
+
+        mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID,
+                Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN);
+        boolean isBlockListedAfterDoNotShowAgain =
+                mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                        DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS);
+
+        mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID,
+                Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG);
+        boolean isBlockListedAfterDoNotShowAgainLong =
+                mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                        DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS);
+
+        assertThat(initiallyBlocklisted).isFalse();
+        assertThat(isBlockListedWhenActive).isFalse();
+        assertThat(isBlockListedAfterDismissed).isTrue();
+        assertThat(isBlockListedAfterDoNotShowAgain).isTrue();
+        assertThat(isBlockListedAfterDoNotShowAgainLong).isTrue();
+    }
+
+    @Test
+    public void testBlocklist_overflow() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DISMISSED, CURRENT_TIME));
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID + 1,
+                new BlocklistElement(Blocklist.BlocklistState.UNKNOWN, CURRENT_TIME));
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID + 2,
+                new BlocklistElement(Blocklist.BlocklistState.UNKNOWN, CURRENT_TIME));
+
+        // blocklist should have evicted HALFSHEET_ID making it no longer blocklisted, this is
+        // because for the test we initialize the size of the blocklist cache to be max = 2
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void removeHalfSheetDismissState() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DISMISSED, CURRENT_TIME));
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse();
+    }
+
+    @Test
+    public void removeHalfSheetBanState() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse();
+    }
+
+    @Test
+    public void testHalfSheetTimeOutReleaseBan() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        when(mClock.elapsedRealtime())
+            .thenReturn(CURRENT_TIME + BLOCKLIST_CANCEL_TIMEOUT_MILLIS + 1);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void testHalfSheetDoNotShowAgainLong() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(
+                        Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG, CURRENT_TIME));
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue();
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+        assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse();
+    }
+
+    @Test
+    public void testHalfSheetDoNotShowAgainLongTimeout() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + DURATION_MILLI_SECONDS_LONG + 1);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void banAllItem_blockHalfSheet() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME));
+
+        mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS);
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS - 1);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+    }
+
+    @Test
+    public void banAllItem_invokeAgainWithShorterDurationTime_blockHalfSheet() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME));
+
+        mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS);
+        // The 2nd invocation time is shorter than the original one so it's ignored.
+        mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS - 1);
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS - 1);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+    }
+
+    @Test
+    public void banAllItem_releaseHalfSheet() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME));
+
+        mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS);
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS);
+
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void banAllItem_extendEndTime_blockHalfSheet() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME));
+
+        mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS);
+        when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS);
+        // Another banAllItem comes so the end time is extended.
+        mFastPairHalfSheetBlocklist.banAllItem(/* banDurationTimeMillis= */ 1);
+
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+    }
+
+    @Test
+    public void testHalfSheetTimeOutFirstDismissWithInDuration() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        when(mClock.elapsedRealtime())
+            .thenReturn(CURRENT_TIME + DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND - 1);
+
+        assertThat(
+                mFastPairHalfSheetBlocklist.isBlocklisted(
+                        HALFSHEET_ID, (int) DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND))
+                .isTrue();
+    }
+
+    @Test
+    public void testHalfSheetTimeOutFirstDismissOutOfDuration() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        when(mClock.elapsedRealtime())
+            .thenReturn(CURRENT_TIME + DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND + 1);
+
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void testHalfSheetReset() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        mFastPairHalfSheetBlocklist.resetBlockState(HALFSHEET_ID);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+                DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+
+    @Test
+    public void testIsStateExpired() {
+        mFastPairHalfSheetBlocklist.put(
+                HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        when(mClock.elapsedRealtime())
+                .thenReturn(CURRENT_TIME + 1);
+        assertThat(mFastPairHalfSheetBlocklist.isStateExpired(HALFSHEET_ID)).isFalse();
+        when(mClock.elapsedRealtime())
+                .thenReturn(CURRENT_TIME +  STATE_EXPIRATION_MILLISECOND + 1);
+        assertThat(mFastPairHalfSheetBlocklist.isStateExpired(HALFSHEET_ID)).isTrue();
+    }
+
+    @Test
+    public void testForceUpdateState() {
+        mFastPairHalfSheetBlocklist.put(HALFSHEET_ID,
+                new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME));
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+            DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue();
+        mFastPairHalfSheetBlocklist.forceUpdateState(HALFSHEET_ID, BlocklistState.ACTIVE);
+        assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID,
+            DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
index 58e4c47..82b9070 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
@@ -16,22 +16,40 @@
 
 package com.android.server.nearby.fastpair.halfsheet;
 
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.ACTIVE;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DISMISSED;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN;
+import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG;
+import static com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager.DISMISS_HALFSHEET_RUNNABLE_NAME;
+import static com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager.SHOW_TOAST_RUNNABLE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.PairStatusMetadata;
 import android.os.UserHandle;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.common.eventloop.NamedRunnable;
 import com.android.server.nearby.common.locator.Locator;
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
 import com.android.server.nearby.fastpair.FastPairController;
@@ -48,53 +66,67 @@
 import service.proto.Cache;
 
 public class FastPairHalfSheetManagerTest {
-    private static final String BLEADDRESS = "11:22:44:66";
+    private static final String MODEL_ID = "model_id";
+    private static final String BLE_ADDRESS = "11:22:44:66";
+    private static final String MODEL_ID_1 = "model_id_1";
+    private static final String BLE_ADDRESS_1 = "99:99:99:99";
     private static final String NAME = "device_name";
+    private static final int PASSKEY = 1234;
+    private static final int SUCCESS = 1001;
+    private static final int FAIL = 1002;
+    private static final String EXTRA_HALF_SHEET_CONTENT =
+            "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
+    private static final String RESULT_FAIL = "RESULT_FAIL";
     private FastPairHalfSheetManager mFastPairHalfSheetManager;
     private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
+    private ResolveInfo mResolveInfo;
+    private List<ResolveInfo> mResolveInfoList;
+    private ApplicationInfo mApplicationInfo;
+    @Mock private Context mContext;
     @Mock
     LocatorContextWrapper mContextWrapper;
     @Mock
-    ResolveInfo mResolveInfo;
-    @Mock
     PackageManager mPackageManager;
     @Mock
     Locator mLocator;
     @Mock
     FastPairController mFastPairController;
+    @Mock
+    EventLoop mEventLoop;
+    @Mock
+    FastPairStatusCallback mFastPairStatusCallback;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
+        mLocator.overrideBindingForTest(EventLoop.class, mEventLoop);
+
+        mResolveInfo = new ResolveInfo();
+        mResolveInfoList = new ArrayList<>();
+        mResolveInfo.activityInfo = new ActivityInfo();
+        mApplicationInfo = new ApplicationInfo();
+        mPackageManager = mock(PackageManager.class);
+
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+        when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
+        when(mContextWrapper.getLocator()).thenReturn(mLocator);
+        when(mLocator.get(EventLoop.class)).thenReturn(mEventLoop);
+        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(mResolveInfoList);
+        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
 
         mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
-                .setAddress(BLEADDRESS)
+                .setModelId(MODEL_ID)
+                .setAddress(BLE_ADDRESS)
                 .setDeviceName(NAME)
                 .build();
     }
 
     @Test
     public void verifyFastPairHalfSheetManagerBehavior() {
-        mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
-        ResolveInfo resolveInfo = new ResolveInfo();
-        List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
-        mPackageManager = mock(PackageManager.class);
-        when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
-        resolveInfo.activityInfo = new ActivityInfo();
-        ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.sourceDir = "/apex/com.android.tethering";
-        applicationInfo.packageName = "test.package";
-        resolveInfo.activityInfo.applicationInfo = applicationInfo;
-        resolveInfoList.add(resolveInfo);
-        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
-        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
-
-        mFastPairHalfSheetManager =
-                new FastPairHalfSheetManager(mContextWrapper);
-
-        when(mContextWrapper.getLocator()).thenReturn(mLocator);
-
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
@@ -105,27 +137,12 @@
 
     @Test
     public void verifyFastPairHalfSheetManagerHalfSheetApkNotValidBehavior() {
-        mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
-        ResolveInfo resolveInfo = new ResolveInfo();
-        List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
-        mPackageManager = mock(PackageManager.class);
-        when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
-        resolveInfo.activityInfo = new ActivityInfo();
-        ApplicationInfo applicationInfo = new ApplicationInfo();
         // application directory is wrong
-        applicationInfo.sourceDir = "/apex/com.android.nearby";
-        applicationInfo.packageName = "test.package";
-        resolveInfo.activityInfo.applicationInfo = applicationInfo;
-        resolveInfoList.add(resolveInfo);
-        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
-        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
-
-        mFastPairHalfSheetManager =
-                new FastPairHalfSheetManager(mContextWrapper);
-
-        when(mContextWrapper.getLocator()).thenReturn(mLocator);
-
+        mApplicationInfo.sourceDir = "/apex/com.android.nearby";
+        mApplicationInfo.packageName = "test.package";
+        mResolveInfo.activityInfo.applicationInfo = mApplicationInfo;
+        mResolveInfoList.add(mResolveInfo);
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
@@ -133,4 +150,420 @@
         verify(mContextWrapper, never())
                 .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
     }
+
+    @Test
+    public void testHalfSheetForegroundState() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        assertThat(mFastPairHalfSheetManager.getHalfSheetForeground()).isTrue();
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+        assertThat(mFastPairHalfSheetManager.getHalfSheetForeground()).isFalse();
+    }
+
+    @Test
+    public void testEmptyMethods() {
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        mFastPairHalfSheetManager.destroyBluetoothPairController();
+        mFastPairHalfSheetManager.notifyPairingProcessDone(true, BLE_ADDRESS, null);
+        mFastPairHalfSheetManager.showPairingFailed();
+        mFastPairHalfSheetManager.showPairingHalfSheet(null);
+        mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS);
+        mFastPairHalfSheetManager.showPasskeyConfirmation(null, PASSKEY);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheetThenDismissOnce_stateDISMISSED() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        //First time dismiss -> state: DISMISSED
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(DISMISSED);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheetThenBan_stateDO_NOT_SHOW_AGAIN() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        //First time ban -> state: DO_NOT_SHOW_AGAIN
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(DO_NOT_SHOW_AGAIN);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheetThenBanTwice_stateDO_NOT_SHOW_AGAIN_LONG() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+        mFastPairHalfSheetManager.dismiss(MODEL_ID);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        //Second time ban -> state: DO_NOT_SHOW_AGAIN
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState())
+                .isEqualTo(DO_NOT_SHOW_AGAIN_LONG);
+    }
+
+    @Test
+    public void testResetBanSate_resetDISMISSEDtoACTIVE() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        mHalfSheetBlocklist.updateState(halfSheetId, DISMISSED);
+        mFastPairHalfSheetManager.resetBanState(MODEL_ID);
+
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE);
+    }
+
+    @Test
+    public void testResetBanSate_resetDO_NOT_SHOW_AGAINtoACTIVE() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        mHalfSheetBlocklist.updateState(halfSheetId, DO_NOT_SHOW_AGAIN);
+        mFastPairHalfSheetManager.resetBanState(MODEL_ID);
+
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE);
+    }
+
+    @Test
+    public void testResetBanSate_resetDO_NOT_SHOW_AGAIN_LONGtoACTIVE() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        mHalfSheetBlocklist.updateState(halfSheetId, DO_NOT_SHOW_AGAIN_LONG
+        );
+        mFastPairHalfSheetManager.resetBanState(MODEL_ID);
+
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE);
+    }
+
+    @Test
+    public void testReportDonePairing() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        assertThat(mFastPairHalfSheetManager.getHalfSheetBlocklist().size()).isEqualTo(1);
+
+        mFastPairHalfSheetManager
+                .reportDonePairing(mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID));
+
+        assertThat(mFastPairHalfSheetManager.getHalfSheetBlocklist().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_AutoDismiss() throws InterruptedException {
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        verifyInitialPairingNameRunnablePostedTimes(1);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiShownAndItemWithTheSameAddress() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID)
+                .setAddress(BLE_ADDRESS)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(1);
+
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+        // When half sheet shown and receives broadcast from the same address,
+        // DO NOT request start-activity to avoid unnecessary memory usage,
+        // Just reset the auto dismiss timeout for the new request
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(2);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiShowAndItemWithDifferentAddressSameModelId() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID)
+                .setAddress(BLE_ADDRESS_1)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(1);
+
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+        // When half sheet shown and receives broadcast from the same model id
+        // but with different address, DO NOT rest the auto dismiss timeout. No action is required.
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(1);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiShowAndItemWithDifferentModelId() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID_1)
+                .setAddress(BLE_ADDRESS_1)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+        verifyInitialPairingNameRunnablePostedTimes(1);
+        verifyHalfSheetActivityIntent(1);
+
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+        // When half sheet shown and receives broadcast from a different model id,
+        // the new request should be ignored. No action is required.
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(1);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiNotShownAndIsPairingWithTheSameAddress() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID)
+                .setAddress(BLE_ADDRESS)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false);
+        mFastPairHalfSheetManager.setIsActivePairing(true);
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+
+        // If the half sheet is not in foreground but the system is still pairing the same device,
+        // mark as duplicate request and skip.
+        verifyHalfSheetActivityIntent(1);
+        verifyInitialPairingNameRunnablePostedTimes(1);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiNotShownAndIsPairingWithADifferentAddress() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID_1)
+                .setAddress(BLE_ADDRESS_1)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false);
+        mFastPairHalfSheetManager.setIsActivePairing(true);
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+
+        // shouldShowHalfSheet
+        verifyHalfSheetActivityIntent(2);
+        verifyInitialPairingNameRunnablePostedTimes(2);
+    }
+
+    @Test
+    public void showInitialPairingHalfSheet_whenUiNotShownAndIsNotPairingWithTheSameAddress() {
+        Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder()
+                .setModelId(MODEL_ID)
+                .setAddress(BLE_ADDRESS)
+                .setDeviceName(NAME)
+                .build();
+        configResolveInfoList();
+        mFastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false);
+        mFastPairHalfSheetManager.setIsActivePairing(false);
+        mFastPairHalfSheetManager.showHalfSheet(testItem);
+
+        // shouldShowHalfSheet
+        verifyHalfSheetActivityIntent(2);
+        verifyInitialPairingNameRunnablePostedTimes(2);
+    }
+
+    @Test
+    public void testReportActivelyPairing() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse();
+
+        mFastPairHalfSheetManager.reportActivelyPairing();
+
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isTrue();
+    }
+
+    @Test
+    public void showPairingSuccessHalfSheetHalfSheetActivityActive_ChangeUIToShowSuccessInfo() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        mFastPairHalfSheetManager.mFastPairUiService
+                .setFastPairStatusCallback(mFastPairStatusCallback);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS);
+
+        verifyFastPairStatusCallback(1, SUCCESS);
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse();
+    }
+
+    @Test
+    public void showPairingSuccessHalfSheetHalfSheetActivityNotActive_showToast() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.setHalfSheetForeground(false);
+        mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS);
+
+        ArgumentCaptor<NamedRunnable> captor = ArgumentCaptor.forClass(NamedRunnable.class);
+
+        verify(mEventLoop).postRunnable(captor.capture());
+        assertThat(
+                captor.getAllValues().stream()
+                        .filter(r -> r.name.equals(SHOW_TOAST_RUNNABLE_NAME))
+                        .count())
+                .isEqualTo(1);
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse();
+    }
+
+    @Test
+    public void showPairingFailedHalfSheetHalfSheetActivityActive_ChangeUIToShowFailedInfo() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        mFastPairHalfSheetManager.mFastPairUiService
+                .setFastPairStatusCallback(mFastPairStatusCallback);
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.showPairingFailed();
+
+        verifyFastPairStatusCallback(1, FAIL);
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse();
+    }
+
+    @Test
+    public void showPairingFailedHalfSheetActivityNotActive_StartHalfSheetToShowFailedInfo() {
+        configResolveInfoList();
+        mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        FastPairHalfSheetBlocklist mHalfSheetBlocklist =
+                mFastPairHalfSheetManager.getHalfSheetBlocklist();
+
+        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+        mFastPairHalfSheetManager.setHalfSheetForeground(false);
+        mFastPairHalfSheetManager.showPairingFailed();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID);
+
+        verify(mContextWrapper, times(2))
+                .startActivityAsUser(captor.capture(), eq(UserHandle.CURRENT));
+        assertThat(
+                captor.getAllValues().stream()
+                        .filter(r ->
+                            r.getStringExtra(EXTRA_HALF_SHEET_CONTENT) != null
+                                    && r.getStringExtra(EXTRA_HALF_SHEET_CONTENT)
+                                    .equals(RESULT_FAIL))
+
+                        .count())
+                .isEqualTo(1);
+        assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse();
+        assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE);
+    }
+
+    private void verifyInitialPairingNameRunnablePostedTimes(int times) {
+        ArgumentCaptor<NamedRunnable> captor = ArgumentCaptor.forClass(NamedRunnable.class);
+
+        verify(mEventLoop, times(times)).postRunnableDelayed(captor.capture(), anyLong());
+        assertThat(
+                captor.getAllValues().stream()
+                        .filter(r -> r.name.equals(DISMISS_HALFSHEET_RUNNABLE_NAME))
+                        .count())
+                .isEqualTo(times);
+    }
+
+    private void verifyHalfSheetActivityIntent(int times) {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+
+        verify(mContextWrapper, times(times))
+                .startActivityAsUser(captor.capture(), eq(UserHandle.CURRENT));
+        assertThat(
+                captor.getAllValues().stream()
+                        .filter(r -> r.getAction().equals("android.nearby.SHOW_HALFSHEET"))
+                        .count())
+                .isEqualTo(times);
+    }
+
+    private void verifyFastPairStatusCallback(int times, int status) {
+        ArgumentCaptor<PairStatusMetadata> captor =
+                ArgumentCaptor.forClass(PairStatusMetadata.class);
+        verify(mFastPairStatusCallback, times(times)).onPairUpdate(any(), captor.capture());
+        assertThat(
+                captor.getAllValues().stream()
+                        .filter(r -> r.getStatus() == status)
+                        .count())
+                .isEqualTo(times);
+    }
+
+    private void configResolveInfoList() {
+        mApplicationInfo.sourceDir = "/apex/com.android.tethering";
+        mApplicationInfo.packageName = "test.package";
+        mResolveInfo.activityInfo.applicationInfo = mApplicationInfo;
+        mResolveInfoList.add(mResolveInfo);
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java
new file mode 100644
index 0000000..d995969
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import static com.android.server.nearby.fastpair.notification.FastPairNotificationBuilder.NOTIFICATION_OVERRIDE_NAME_EXTRA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FastPairNotificationBuilderTest {
+
+    private static final String STRING_DEVICE = "Devices";
+    private static final String STRING_NEARBY = "Nearby";
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
+    @Mock private Resources mResources;
+
+    private ResolveInfo mResolveInfo;
+    private List<ResolveInfo> mResolveInfoList;
+    private ApplicationInfo mApplicationInfo;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        HalfSheetResources.setResourcesContextForTest(mContext);
+
+        mResolveInfo = new ResolveInfo();
+        mResolveInfoList = new ArrayList<>();
+        mResolveInfo.activityInfo = new ActivityInfo();
+        mApplicationInfo = new ApplicationInfo();
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getApplicationInfo())
+                .thenReturn(InstrumentationRegistry
+                        .getInstrumentation().getContext().getApplicationInfo());
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(mResolveInfoList);
+        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
+        mApplicationInfo.sourceDir = "/apex/com.android.nearby";
+        mApplicationInfo.packageName = "test.package";
+        mResolveInfo.activityInfo.applicationInfo = mApplicationInfo;
+        mResolveInfoList.add(mResolveInfo);
+
+        when(mResources.getString(eq(R.string.common_devices))).thenReturn(STRING_DEVICE);
+        when(mResources.getString(eq(R.string.common_nearby_title))).thenReturn(STRING_NEARBY);
+    }
+
+    @Test
+    public void setIsDevice_true() {
+        Notification notification =
+                new FastPairNotificationBuilder(mContext, "channelId")
+                        .setIsDevice(true).build();
+        assertThat(notification.extras.getString(NOTIFICATION_OVERRIDE_NAME_EXTRA))
+                .isEqualTo(STRING_DEVICE);
+    }
+
+    @Test
+    public void setIsDevice_false() {
+        Notification notification =
+                new FastPairNotificationBuilder(mContext, "channelId")
+                        .setIsDevice(false).build();
+        assertThat(notification.extras.getString(NOTIFICATION_OVERRIDE_NAME_EXTRA))
+                .isEqualTo(STRING_NEARBY);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java
new file mode 100644
index 0000000..9670a3f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+
+public class FastPairNotificationManagerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    NotificationManager mNotificationManager;
+    @Mock
+    Resources mResources;
+    @Mock
+    private LocatorContextWrapper mLocatorContextWrapper;
+    @Mock
+    private Locator mLocator;
+
+    private static final int NOTIFICATION_ID = 1;
+    private static final int BATTERY_LEVEL = 1;
+    private static final String DEVICE_NAME = "deviceName";
+    private FastPairNotificationManager mFastPairNotificationManager;
+    private DiscoveryItem mItem;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+        when(mLocatorContextWrapper.getResources()).thenReturn(mResources);
+        when(mLocatorContextWrapper.getLocator()).thenReturn(mLocator);
+        HalfSheetResources.setResourcesContextForTest(mLocatorContextWrapper);
+        // Real context is needed
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        FastPairNotificationManager fastPairNotificationManager =
+        mFastPairNotificationManager =
+                new FastPairNotificationManager(context, NOTIFICATION_ID, mNotificationManager,
+                        new HalfSheetResources(mLocatorContextWrapper));
+        mLocator.overrideBindingForTest(FastPairNotificationManager.class,
+                fastPairNotificationManager);
+
+        mItem = new DiscoveryItem(mLocatorContextWrapper,
+                Cache.StoredDiscoveryItem.newBuilder().setTitle("Device Name").build());
+    }
+
+    @Test
+    public void  notifyPairingProcessDone() {
+        mFastPairNotificationManager.notifyPairingProcessDone(true, true,
+                "privateAddress", "publicAddress");
+    }
+
+    @Test
+    public void  showConnectingNotification() {
+        mFastPairNotificationManager.showConnectingNotification(mItem);
+    }
+
+    @Test
+    public void   showPairingFailedNotification() {
+        mFastPairNotificationManager
+                .showPairingFailedNotification(mItem, new byte[]{1});
+    }
+
+    @Test
+    public void  showPairingSucceededNotification() {
+        mFastPairNotificationManager
+                .showPairingSucceededNotification(mItem, BATTERY_LEVEL, DEVICE_NAME);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java
new file mode 100644
index 0000000..cfebbde
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.fastpair.notification;
+
+import static com.android.server.nearby.fastpair.notification.FastPairNotificationManager.DEVICES_WITHIN_REACH_CHANNEL_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.nearby.halfsheet.R;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+
+public class FastPairNotificationsTest {
+    private static final Cache.StoredDiscoveryItem SCAN_FAST_PAIR_ITEM =
+            Cache.StoredDiscoveryItem.newBuilder()
+                    .setDeviceName("TestName")
+                    .build();
+    private static final String STRING_DEVICE = "Devices";
+    private static final String STRING_NEARBY = "Nearby";
+    private static final String STRING_YOUR_DEVICE = "Your saved device is available";
+    private static final String STRING_CONNECTING = "Connecting";
+    private static final String STRING_DEVICE_READY = "Device connected";
+
+    private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+    @Mock
+    LocatorContextWrapper mContextWrapper;
+    @Mock
+    Locator mLocator;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private Drawable mDrawable;
+
+    private DiscoveryItem mItem;
+    private HalfSheetResources mHalfSheetResources;
+    private FastPairNotifications mFastPairNotifications;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mHalfSheetResources = new HalfSheetResources(mContext);
+        Context realContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mFastPairNotifications =
+                new FastPairNotifications(realContext, mHalfSheetResources);
+        HalfSheetResources.setResourcesContextForTest(mContext);
+
+        when(mContextWrapper.getLocator()).thenReturn(mLocator);
+        when(mContext.getResources()).thenReturn(mResources);
+
+        when(mResources.getString(eq(R.string.common_devices))).thenReturn(STRING_DEVICE);
+        when(mResources.getString(eq(R.string.common_nearby_title))).thenReturn(STRING_NEARBY);
+        when(mResources.getString(eq(R.string.fast_pair_your_device)))
+                .thenReturn(STRING_YOUR_DEVICE);
+        when(mResources.getString(eq(R.string.common_connecting))).thenReturn(STRING_CONNECTING);
+        when(mResources.getString(eq(R.string.fast_pair_device_ready)))
+                .thenReturn(STRING_DEVICE_READY);
+        when(mResources.getDrawable(eq(R.drawable.quantum_ic_devices_other_vd_theme_24), any()))
+                .thenReturn(mDrawable);
+
+        mItem = new DiscoveryItem(mContextWrapper, SCAN_FAST_PAIR_ITEM);
+    }
+
+    @Test
+    public void verify_progressNotification() {
+        Notification notification = mFastPairNotifications.progressNotification(mItem);
+
+        assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID);
+        assertThat(notification.getSmallIcon().getResId())
+                .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24);
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_PROGRESS);
+        assertThat(notification.tickerText.toString()).isEqualTo(STRING_CONNECTING);
+    }
+
+    @Test
+    public void verify_discoveryNotification() {
+        Notification notification =
+                mFastPairNotifications.discoveryNotification(mItem, ACCOUNT_KEY);
+
+        assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID);
+        assertThat(notification.getSmallIcon().getResId())
+                .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24);
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_RECOMMENDATION);
+    }
+
+    @Test
+    public void verify_succeededNotification() {
+        Notification notification = mFastPairNotifications
+                .pairingSucceededNotification(101, null, "model name", mItem);
+
+        assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID);
+        assertThat(notification.getSmallIcon().getResId())
+                .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24);
+        assertThat(notification.tickerText.toString()).isEqualTo(STRING_DEVICE_READY);
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_STATUS);
+    }
+
+    @Test
+    public void verify_failedNotification() {
+        Notification notification =
+                mFastPairNotifications.showPairingFailedNotification(mItem, ACCOUNT_KEY);
+
+        assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID);
+        assertThat(notification.getSmallIcon().getResId())
+                .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24);
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java
new file mode 100644
index 0000000..2d496fd
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.fastpair.pairinghandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import service.proto.Cache;
+import service.proto.Rpcs;
+
+public class HalfSheetPairingProgressHandlerTest {
+    @Mock
+    Locator mLocator;
+    @Mock
+    LocatorContextWrapper mContextWrapper;
+    @Mock
+    Clock mClock;
+    @Mock
+    FastPairCacheManager mFastPairCacheManager;
+    @Mock
+    FastPairConnection mFastPairConnection;
+    @Mock
+    FootprintsDeviceManager mFootprintsDeviceManager;
+    @Mock
+    EventLoop mEventLoop;
+
+    private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+    private static final int SUBSEQUENT_PAIR_START = 1310;
+    private static final int SUBSEQUENT_PAIR_END = 1320;
+    private static final int PASSKEY = 1234;
+    private static HalfSheetPairingProgressHandler sHalfSheetPairingProgressHandler;
+    private static DiscoveryItem sDiscoveryItem;
+    private static BluetoothDevice sBluetoothDevice;
+    private static FastPairHalfSheetManager sFastPairHalfSheetManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mContextWrapper.getLocator()).thenReturn(mLocator);
+        mLocator.overrideBindingForTest(FastPairCacheManager.class, mFastPairCacheManager);
+        mLocator.overrideBindingForTest(Clock.class, mClock);
+        sFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper);
+        mLocator.bind(FastPairHalfSheetManager.class, sFastPairHalfSheetManager);
+        when(mLocator.get(FastPairHalfSheetManager.class)).thenReturn(sFastPairHalfSheetManager);
+        when(mLocator.get(EventLoop.class)).thenReturn(mEventLoop);
+        sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+        sDiscoveryItem.setStoredItemForTest(
+                sDiscoveryItem.getStoredItemForTest().toBuilder()
+                        .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+                        .setMacAddress(MAC_ADDRESS)
+                        .setFastPairInformation(
+                                Cache.FastPairInformation.newBuilder()
+                                        .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+                        .build());
+        sHalfSheetPairingProgressHandler =
+                new HalfSheetPairingProgressHandler(mContextWrapper, sDiscoveryItem,
+                        sDiscoveryItem.getAppPackageName(), ACCOUNT_KEY);
+
+        sBluetoothDevice =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+    }
+
+    @Test
+    public void getPairEndEventCode() {
+        assertThat(sHalfSheetPairingProgressHandler
+                .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END);
+    }
+
+    @Test
+    public void getPairStartEventCode() {
+        assertThat(sHalfSheetPairingProgressHandler
+                .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START);
+    }
+
+    @Test
+    public void testOnHandlePasskeyConfirmation() {
+        sHalfSheetPairingProgressHandler.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY);
+    }
+
+    @Test
+    public void testOnPairedCallbackCalled() {
+        sHalfSheetPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection, ACCOUNT_KEY,
+                mFootprintsDeviceManager, MAC_ADDRESS);
+    }
+
+    @Test
+    public void testonPairingFailed() {
+        Throwable e = new Throwable("onPairingFailed");
+        sHalfSheetPairingProgressHandler.onPairingFailed(e);
+    }
+
+    @Test
+    public void testonPairingStarted() {
+        sHalfSheetPairingProgressHandler.onPairingStarted();
+        assertThat(sFastPairHalfSheetManager.isActivePairing()).isTrue();
+    }
+
+    @Test
+    public void testonPairingSuccess() {
+        sHalfSheetPairingProgressHandler.onPairingSuccess(MAC_ADDRESS);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java
new file mode 100644
index 0000000..5c61ddb
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.fastpair.pairinghandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.HalfSheetResources;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import service.proto.Cache;
+import service.proto.Rpcs;
+
+public class NotificationPairingProgressHandlerTest {
+
+    @Mock
+    Locator mLocator;
+    @Mock
+    LocatorContextWrapper mContextWrapper;
+    @Mock
+    Clock mClock;
+    @Mock
+    FastPairCacheManager mFastPairCacheManager;
+    @Mock
+    FastPairConnection mFastPairConnection;
+    @Mock
+    FootprintsDeviceManager mFootprintsDeviceManager;
+    @Mock
+    android.bluetooth.BluetoothManager mBluetoothManager;
+    @Mock
+    NotificationManager mNotificationManager;
+    @Mock
+    Resources mResources;
+
+    private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+    private static final int SUBSEQUENT_PAIR_START = 1310;
+    private static final int SUBSEQUENT_PAIR_END = 1320;
+    private static DiscoveryItem sDiscoveryItem;
+    private static  NotificationPairingProgressHandler sNotificationPairingProgressHandler;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mContextWrapper.getSystemService(BluetoothManager.class))
+                .thenReturn(mBluetoothManager);
+        when(mContextWrapper.getLocator()).thenReturn(mLocator);
+        when(mContextWrapper.getResources()).thenReturn(mResources);
+        HalfSheetResources.setResourcesContextForTest(mContextWrapper);
+
+        mLocator.overrideBindingForTest(FastPairCacheManager.class,
+                mFastPairCacheManager);
+        mLocator.overrideBindingForTest(Clock.class, mClock);
+        sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+        sDiscoveryItem.setStoredItemForTest(
+                sDiscoveryItem.getStoredItemForTest().toBuilder()
+                        .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+                        .setFastPairInformation(
+                                Cache.FastPairInformation.newBuilder()
+                                        .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+                        .build());
+        sNotificationPairingProgressHandler = createProgressHandler(ACCOUNT_KEY, sDiscoveryItem);
+    }
+
+    @Test
+    public void getPairEndEventCode() {
+        assertThat(sNotificationPairingProgressHandler
+                .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END);
+    }
+
+    @Test
+    public void getPairStartEventCode() {
+        assertThat(sNotificationPairingProgressHandler
+                .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START);
+    }
+
+    @Test
+    public void onReadyToPair() {
+        sNotificationPairingProgressHandler.onReadyToPair();
+    }
+
+    @Test
+    public void onPairedCallbackCalled() {
+        sNotificationPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection,
+                    ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS);
+    }
+
+    @Test
+    public void  onPairingFailed() {
+        Throwable e = new Throwable("Pairing Failed");
+        sNotificationPairingProgressHandler.onPairingFailed(e);
+    }
+
+    @Test
+    public void onPairingSuccess() {
+        sNotificationPairingProgressHandler.onPairingSuccess(sDiscoveryItem.getMacAddress());
+    }
+
+    private NotificationPairingProgressHandler createProgressHandler(
+            @Nullable byte[] accountKey, DiscoveryItem fastPairItem) {
+        FastPairHalfSheetManager fastPairHalfSheetManager =
+                new FastPairHalfSheetManager(mContextWrapper);
+        // Real context is needed
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        FastPairNotificationManager fastPairNotificationManager =
+                new FastPairNotificationManager(context, 1234, mNotificationManager,
+                        new HalfSheetResources(mContextWrapper));
+        mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager);
+        mLocator.overrideBindingForTest(FastPairNotificationManager.class,
+                fastPairNotificationManager);
+
+        NotificationPairingProgressHandler mNotificationPairingProgressHandler =
+                new NotificationPairingProgressHandler(
+                        mContextWrapper,
+                        fastPairItem,
+                        fastPairItem.getAppPackageName(),
+                        accountKey,
+                        fastPairNotificationManager);
+        return mNotificationPairingProgressHandler;
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
index 2ade5f2..6d769df 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
@@ -20,10 +20,21 @@
 
 import static org.mockito.Mockito.when;
 
-import androidx.annotation.Nullable;
+import android.app.NotificationManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.res.Resources;
 
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
 import com.android.server.nearby.common.locator.Locator;
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.HalfSheetResources;
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
 import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
 import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
@@ -42,8 +53,8 @@
 
 import service.proto.Cache;
 import service.proto.Rpcs;
-
 public class PairingProgressHandlerBaseTest {
+
     @Mock
     Locator mLocator;
     @Mock
@@ -54,24 +65,53 @@
     FastPairCacheManager mFastPairCacheManager;
     @Mock
     FootprintsDeviceManager mFootprintsDeviceManager;
+    @Mock
+    FastPairConnection mFastPairConnection;
+    @Mock
+    BluetoothManager mBluetoothManager;
+    @Mock
+    Resources mResources;
+    @Mock
+    NotificationManager mNotificationManager;
+
+    private static final String MAC_ADDRESS = "00:11:22:33:44:55";
     private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+    private static final int PASSKEY = 1234;
+    private static DiscoveryItem sDiscoveryItem;
+    private static PairingProgressHandlerBase sPairingProgressHandlerBase;
+    private static BluetoothDevice sBluetoothDevice;
 
     @Before
     public void setup() {
-
         MockitoAnnotations.initMocks(this);
+        when(mContextWrapper.getSystemService(BluetoothManager.class))
+                .thenReturn(mBluetoothManager);
         when(mContextWrapper.getLocator()).thenReturn(mLocator);
         mLocator.overrideBindingForTest(FastPairCacheManager.class,
                 mFastPairCacheManager);
         mLocator.overrideBindingForTest(Clock.class, mClock);
+        when(mContextWrapper.getResources()).thenReturn(mResources);
+        HalfSheetResources.setResourcesContextForTest(mContextWrapper);
+
+        sBluetoothDevice =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+        sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+        sDiscoveryItem.setStoredItemForTest(
+                sDiscoveryItem.getStoredItemForTest().toBuilder()
+                        .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+                        .setFastPairInformation(
+                                Cache.FastPairInformation.newBuilder()
+                                        .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+                        .build());
+
+        sPairingProgressHandlerBase =
+                createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false);
     }
 
     @Test
     public void createHandler_halfSheetSubsequentPairing_notificationPairingHandlerCreated() {
-
-        DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
-        discoveryItem.setStoredItemForTest(
-                discoveryItem.getStoredItemForTest().toBuilder()
+        sDiscoveryItem.setStoredItemForTest(
+                sDiscoveryItem.getStoredItemForTest().toBuilder()
                         .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
                         .setFastPairInformation(
                                 Cache.FastPairInformation.newBuilder()
@@ -79,7 +119,7 @@
                         .build());
 
         PairingProgressHandlerBase progressHandler =
-                createProgressHandler(ACCOUNT_KEY, discoveryItem, /* isRetroactivePair= */ false);
+                createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false);
 
         assertThat(progressHandler).isInstanceOf(NotificationPairingProgressHandler.class);
     }
@@ -87,26 +127,107 @@
     @Test
     public void createHandler_halfSheetInitialPairing_halfSheetPairingHandlerCreated() {
         // No account key
-        DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
-        discoveryItem.setStoredItemForTest(
-                discoveryItem.getStoredItemForTest().toBuilder()
+        sDiscoveryItem.setStoredItemForTest(
+                sDiscoveryItem.getStoredItemForTest().toBuilder()
                         .setFastPairInformation(
                                 Cache.FastPairInformation.newBuilder()
                                         .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
                         .build());
 
         PairingProgressHandlerBase progressHandler =
-                createProgressHandler(null, discoveryItem, /* isRetroactivePair= */ false);
+                createProgressHandler(null, sDiscoveryItem, /* isRetroactivePair= */ false);
 
         assertThat(progressHandler).isInstanceOf(HalfSheetPairingProgressHandler.class);
     }
 
+    @Test
+    public void onPairingStarted() {
+        sPairingProgressHandlerBase.onPairingStarted();
+    }
+
+    @Test
+    public void onWaitForScreenUnlock() {
+        sPairingProgressHandlerBase.onWaitForScreenUnlock();
+    }
+
+    @Test
+    public void  onScreenUnlocked() {
+        sPairingProgressHandlerBase.onScreenUnlocked();
+    }
+
+    @Test
+    public void onReadyToPair() {
+        sPairingProgressHandlerBase.onReadyToPair();
+    }
+
+    @Test
+    public void  onSetupPreferencesBuilder() {
+        Preferences.Builder prefsBuilder =
+                Preferences.builder()
+                        .setEnableBrEdrHandover(false)
+                        .setIgnoreDiscoveryError(true);
+        sPairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
+    }
+
+    @Test
+    public void  onPairingSetupCompleted() {
+        sPairingProgressHandlerBase.onPairingSetupCompleted();
+    }
+
+    @Test
+    public void onHandlePasskeyConfirmation() {
+        sPairingProgressHandlerBase.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY);
+    }
+
+    @Test
+    public void getKeyForLocalCache() {
+        FastPairConnection.SharedSecret sharedSecret =
+                FastPairConnection.SharedSecret.create(ACCOUNT_KEY, sDiscoveryItem.getMacAddress());
+        sPairingProgressHandlerBase
+                .getKeyForLocalCache(ACCOUNT_KEY, mFastPairConnection, sharedSecret);
+    }
+
+    @Test
+    public void onPairedCallbackCalled() {
+        sPairingProgressHandlerBase.onPairedCallbackCalled(mFastPairConnection,
+                ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS);
+    }
+
+    @Test
+    public void onPairingFailed() {
+        Throwable e = new Throwable("Pairing Failed");
+        sPairingProgressHandlerBase.onPairingFailed(e);
+    }
+
+    @Test
+    public void onPairingSuccess() {
+        sPairingProgressHandlerBase.onPairingSuccess(sDiscoveryItem.getMacAddress());
+    }
+
+    @Test
+    public void  optInFootprintsForInitialPairing() {
+        sPairingProgressHandlerBase.optInFootprintsForInitialPairing(
+                mFootprintsDeviceManager, sDiscoveryItem, ACCOUNT_KEY, null);
+    }
+
+    @Test
+    public void skipWaitingScreenUnlock() {
+        assertThat(sPairingProgressHandlerBase.skipWaitingScreenUnlock()).isFalse();
+    }
+
     private PairingProgressHandlerBase createProgressHandler(
             @Nullable byte[] accountKey, DiscoveryItem fastPairItem, boolean isRetroactivePair) {
-        FastPairNotificationManager fastPairNotificationManager =
-                new FastPairNotificationManager(mContextWrapper, fastPairItem, true);
+
         FastPairHalfSheetManager fastPairHalfSheetManager =
                 new FastPairHalfSheetManager(mContextWrapper);
+        // Real context is needed
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        FastPairNotificationManager fastPairNotificationManager =
+                new FastPairNotificationManager(context, 1234, mNotificationManager,
+                        new HalfSheetResources(mContextWrapper));
+
+        mLocator.overrideBindingForTest(FastPairNotificationManager.class,
+                fastPairNotificationManager);
         mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager);
         PairingProgressHandlerBase pairingProgressHandlerBase =
                 PairingProgressHandlerBase.create(
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
index c406e47..cdec04d 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
@@ -22,10 +22,17 @@
 import service.proto.Cache;
 
 public class FakeDiscoveryItems {
-    public static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
-    public static final long DEFAULT_TIMESTAMP = 1000000000L;
-    public static final String DEFAULT_DESCRIPITON = "description";
-    public static final String TRIGGER_ID = "trigger.id";
+    private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final long DEFAULT_TIMESTAMP = 1000000000L;
+    private static final String DEFAULT_DESCRIPITON = "description";
+    private static final String APP_NAME = "app_name";
+    private static final String ACTION_URL =
+            "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;"
+                    + "package=com.google.android.gms;"
+                    + "component=com.google.android.gms/"
+                    + ".nearby.discovery.service.DiscoveryService;end";
+    private static final String DISPLAY_URL = "DISPLAY_URL";
+    private static final String TRIGGER_ID = "trigger.id";
     private static final String FAST_PAIR_ID = "id";
     private static final int RSSI = -80;
     private static final int TX_POWER = -10;
@@ -46,9 +53,36 @@
         item.setMacAddress(DEFAULT_MAC_ADDRESS);
         item.setFirstObservationTimestampMillis(DEFAULT_TIMESTAMP);
         item.setLastObservationTimestampMillis(DEFAULT_TIMESTAMP);
+        item.setActionUrl(ACTION_URL);
+        item.setAppName(APP_NAME);
         item.setRssi(RSSI);
         item.setTxPower(TX_POWER);
+        item.setDisplayUrl(DISPLAY_URL);
         return item.build();
     }
 
+    public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem(String id,
+            String description, String triggerId, String macAddress, String title,
+            int rssi, int txPower) {
+        Cache.StoredDiscoveryItem.Builder item = Cache.StoredDiscoveryItem.newBuilder();
+        item.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+        if (id != null) {
+            item.setId(id);
+        }
+        if (description != null) {
+            item.setDescription(description);
+        }
+        if (triggerId != null) {
+            item.setTriggerId(triggerId);
+        }
+        if (macAddress != null) {
+            item.setMacAddress(macAddress);
+        }
+        if (title != null) {
+            item.setTitle(title);
+        }
+        item.setRssi(rssi);
+        item.setTxPower(txPower);
+        return item.build();
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
index b261b26..c9a4533 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
@@ -41,7 +41,7 @@
     }
 
     @SuppressWarnings("nullness") // due to passing in this before initialized.
-    private MockingLocator(Context context, Locator locator) {
+    public MockingLocator(Context context, Locator locator) {
         super(context, locator);
         this.mLocatorContextWrapper = new LocatorContextWrapper(context, this);
     }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java
new file mode 100644
index 0000000..b577064
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.injector;
+
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ContextHubManagerAdapterTest {
+    private ContextHubManagerAdapter mContextHubManagerAdapter;
+
+    @Mock
+    ContextHubManager mContextHubManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContextHubManagerAdapter = new ContextHubManagerAdapter(mContextHubManager);
+    }
+
+    @Test
+    public void getContextHubs() {
+        mContextHubManagerAdapter.getContextHubs();
+    }
+
+    @Test
+    public void queryNanoApps() {
+        mContextHubManagerAdapter.queryNanoApps(new ContextHubInfo());
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java
new file mode 100644
index 0000000..e186709
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.nearby.BroadcastRequest;
+
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Unit test for {@link DataElementHeader}.
+ */
+public class DataElementHeaderTest {
+
+    private static final int VERSION = BroadcastRequest.PRESENCE_VERSION_V1;
+
+    @Test
+    public void test_illegalLength() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new DataElementHeader(VERSION, 12, 128));
+    }
+
+    @Test
+    public void test_singeByteConversion() {
+        DataElementHeader header = new DataElementHeader(VERSION, 12, 3);
+        byte[] bytes = header.toBytes();
+        assertThat(bytes).isEqualTo(new byte[]{(byte) 0b00111100});
+
+        DataElementHeader afterConversionHeader = DataElementHeader.fromBytes(VERSION, bytes);
+        assertThat(afterConversionHeader.getDataLength()).isEqualTo(3);
+        assertThat(afterConversionHeader.getDataType()).isEqualTo(12);
+    }
+
+    @Test
+    public void test_multipleBytesConversion() {
+        DataElementHeader header = new DataElementHeader(VERSION, 6, 100);
+        DataElementHeader afterConversionHeader =
+                DataElementHeader.fromBytes(VERSION, header.toBytes());
+        assertThat(afterConversionHeader.getDataLength()).isEqualTo(100);
+        assertThat(afterConversionHeader.getDataType()).isEqualTo(6);
+    }
+
+    @Test
+    public void test_fromBytes() {
+        // Single byte case.
+        byte[] singleByte = new byte[]{(byte) 0b01011101};
+        DataElementHeader singeByteHeader = DataElementHeader.fromBytes(VERSION, singleByte);
+        assertThat(singeByteHeader.getDataLength()).isEqualTo(5);
+        assertThat(singeByteHeader.getDataType()).isEqualTo(13);
+
+        // Two bytes case.
+        byte[] twoBytes = new byte[]{(byte) 0b11011101, (byte) 0b01011101};
+        DataElementHeader twoBytesHeader = DataElementHeader.fromBytes(VERSION, twoBytes);
+        assertThat(twoBytesHeader.getDataLength()).isEqualTo(93);
+        assertThat(twoBytesHeader.getDataType()).isEqualTo(93);
+
+        // Three bytes case.
+        byte[] threeBytes = new byte[]{(byte) 0b11011101, (byte) 0b11111111, (byte) 0b01011101};
+        DataElementHeader threeBytesHeader = DataElementHeader.fromBytes(VERSION, threeBytes);
+        assertThat(threeBytesHeader.getDataLength()).isEqualTo(93);
+        assertThat(threeBytesHeader.getDataType()).isEqualTo(16349);
+
+        // Four bytes case.
+        byte[] fourBytes = new byte[]{
+                (byte) 0b11011101, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b01011101};
+
+        DataElementHeader fourBytesHeader = DataElementHeader.fromBytes(VERSION, fourBytes);
+        assertThat(fourBytesHeader.getDataLength()).isEqualTo(93);
+        assertThat(fourBytesHeader.getDataType()).isEqualTo(2097117);
+    }
+
+    @Test
+    public void test_fromBytesIllegal_singleByte() {
+        assertThrows(IllegalArgumentException.class,
+                () -> DataElementHeader.fromBytes(VERSION, new byte[]{(byte) 0b11011101}));
+    }
+
+    @Test
+    public void test_fromBytesIllegal_twoBytes_wrongFirstByte() {
+        assertThrows(IllegalArgumentException.class,
+                () -> DataElementHeader.fromBytes(VERSION,
+                        new byte[]{(byte) 0b01011101, (byte) 0b01011101}));
+    }
+
+    @Test
+    public void test_fromBytesIllegal_twoBytes_wrongLastByte() {
+        assertThrows(IllegalArgumentException.class,
+                () -> DataElementHeader.fromBytes(VERSION,
+                        new byte[]{(byte) 0b11011101, (byte) 0b11011101}));
+    }
+
+    @Test
+    public void test_fromBytesIllegal_threeBytes() {
+        assertThrows(IllegalArgumentException.class,
+                () -> DataElementHeader.fromBytes(VERSION,
+                        new byte[]{(byte) 0b11011101, (byte) 0b11011101, (byte) 0b11011101}));
+    }
+
+    @Test
+    public void test_multipleBytesConversion_largeNumber() {
+        DataElementHeader header = new DataElementHeader(VERSION, 22213546, 66);
+        DataElementHeader afterConversionHeader =
+                DataElementHeader.fromBytes(VERSION, header.toBytes());
+        assertThat(afterConversionHeader.getDataLength()).isEqualTo(66);
+        assertThat(afterConversionHeader.getDataType()).isEqualTo(22213546);
+    }
+
+    @Test
+    public void test_isExtending() {
+        assertThat(DataElementHeader.isExtending((byte) 0b10000100)).isTrue();
+        assertThat(DataElementHeader.isExtending((byte) 0b01110100)).isFalse();
+        assertThat(DataElementHeader.isExtending((byte) 0b00000000)).isFalse();
+    }
+
+    @Test
+    public void test_convertTag() {
+        assertThat(DataElementHeader.convertTag(true)).isEqualTo((byte) 128);
+        assertThat(DataElementHeader.convertTag(false)).isEqualTo(0);
+    }
+
+    @Test
+    public void test_getHeaderValue() {
+        assertThat(DataElementHeader.getHeaderValue((byte) 0b10000100)).isEqualTo(4);
+        assertThat(DataElementHeader.getHeaderValue((byte) 0b00000100)).isEqualTo(4);
+        assertThat(DataElementHeader.getHeaderValue((byte) 0b11010100)).isEqualTo(84);
+        assertThat(DataElementHeader.getHeaderValue((byte) 0b01010100)).isEqualTo(84);
+    }
+
+    @Test
+    public void test_convertTypeMultipleIntList() {
+        List<Byte> list = DataElementHeader.convertTypeMultipleBytes(128);
+        assertThat(list.size()).isEqualTo(2);
+        assertThat(list.get(0)).isEqualTo((byte) 0b10000001);
+        assertThat(list.get(1)).isEqualTo((byte) 0b00000000);
+
+        List<Byte> list2 = DataElementHeader.convertTypeMultipleBytes(10);
+        assertThat(list2.size()).isEqualTo(1);
+        assertThat(list2.get(0)).isEqualTo((byte) 0b00001010);
+
+        List<Byte> list3 = DataElementHeader.convertTypeMultipleBytes(5242398);
+        assertThat(list3.size()).isEqualTo(4);
+        assertThat(list3.get(0)).isEqualTo((byte) 0b10000010);
+        assertThat(list3.get(1)).isEqualTo((byte) 0b10111111);
+        assertThat(list3.get(2)).isEqualTo((byte) 0b11111100);
+        assertThat(list3.get(3)).isEqualTo((byte) 0b00011110);
+    }
+
+    @Test
+    public void test_getTypeMultipleBytes() {
+        byte[] inputBytes = new byte[]{(byte) 0b11011000, (byte) 0b10000000, (byte) 0b00001001};
+        // 0b101100000000000001001
+        assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes)).isEqualTo(1441801);
+
+        byte[] inputBytes2 = new byte[]{(byte) 0b00010010};
+        assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes2)).isEqualTo(18);
+
+        byte[] inputBytes3 = new byte[]{(byte) 0b10000001, (byte) 0b00000000};
+        assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes3)).isEqualTo(128);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java
new file mode 100644
index 0000000..895df69
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.BroadcastRequest;
+import android.nearby.DataElement;
+import android.nearby.PresenceBroadcastRequest;
+import android.nearby.PresenceCredential;
+import android.nearby.PrivateCredential;
+import android.nearby.PublicCredential;
+
+import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
+import com.android.server.nearby.util.encryption.CryptorImpV1;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ExtendedAdvertisementTest {
+    private static final int IDENTITY_TYPE = PresenceCredential.IDENTITY_TYPE_PRIVATE;
+    private static final int DATA_TYPE_MODEL_ID = 7;
+    private static final int DATA_TYPE_BLE_ADDRESS = 101;
+    private static final int DATA_TYPE_PUBLIC_IDENTITY = 3;
+    private static final byte[] MODE_ID_DATA =
+            new byte[]{2, 1, 30, 2, 10, -16, 6, 22, 44, -2, -86, -69, -52};
+    private static final byte[] BLE_ADDRESS = new byte[]{124, 4, 56, 60, 120, -29, -90};
+    private static final DataElement MODE_ID_ADDRESS_ELEMENT =
+            new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA);
+    private static final DataElement BLE_ADDRESS_ELEMENT =
+            new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS);
+
+    private static final byte[] IDENTITY =
+            new byte[]{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4};
+    private static final int MEDIUM_TYPE_BLE = 0;
+    private static final byte[] SALT = {2, 3};
+    private static final int PRESENCE_ACTION_1 = 1;
+    private static final int PRESENCE_ACTION_2 = 2;
+
+    private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY =
+            new byte[]{-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88};
+    private static final byte[] PUBLIC_KEY =
+            new byte[] {
+                    48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61,
+                    66, 0, 4, -56, -39, -92, 69, 0, 52, 23, 67, 83, -14, 75, 52, -14, -5, -41, 48,
+                    -83, 31, 42, -39, 102, -13, 22, -73, -73, 86, 30, -96, -84, -13, 4, 122, 104,
+                    -65, 64, 91, -109, -45, -35, -56, 55, -79, 47, -85, 27, -96, -119, -82, -80,
+                    123, 41, -119, -25, 1, -112, 112
+            };
+    private static final byte[] ENCRYPTED_METADATA_BYTES =
+            new byte[] {
+                    -44, -25, -95, -124, -7, 90, 116, -8, 7, -120, -23, -22, -106, -44, -19, 61,
+                    -18, 39, 29, 78, 108, -11, -39, 85, -30, 64, -99, 102, 65, 37, -42, 114, -37,
+                    88, -112, 8, -75, -53, 23, -16, -104, 67, 49, 48, -53, 73, -109, 44, -23, -11,
+                    -118, -61, -37, -104, 60, 105, 115, 1, 56, -89, -107, -45, -116, -1, -25, 84,
+                    -19, -128, 81, 11, 92, 77, -58, 82, 122, 123, 31, -87, -57, 70, 23, -81, 7, 2,
+                    -114, -83, 74, 124, -68, -98, 47, 91, 9, 48, -67, 41, -7, -97, 78, 66, -65, 58,
+                    -4, -46, -30, -85, -50, 100, 46, -66, -128, 7, 66, 9, 88, 95, 12, -13, 81, -91,
+            };
+    private static final byte[] METADATA_ENCRYPTION_KEY_TAG =
+            new byte[] {-126, -104, 1, -1, 26, -46, -68, -86};
+    private static final String DEVICE_NAME = "test_device";
+
+    private PresenceBroadcastRequest.Builder mBuilder;
+    private PrivateCredential mPrivateCredential;
+    private PublicCredential mPublicCredential;
+
+    @Before
+    public void setUp() {
+        mPrivateCredential =
+                new PrivateCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, IDENTITY, DEVICE_NAME)
+                        .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+                        .build();
+        mPublicCredential =
+                new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY,
+                        ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG)
+                        .build();
+        mBuilder =
+                new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
+                        SALT, mPrivateCredential)
+                        .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
+                        .addAction(PRESENCE_ACTION_1)
+                        .addAction(PRESENCE_ACTION_2)
+                        .addExtendedProperty(new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS))
+                        .addExtendedProperty(new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA));
+    }
+
+    @Test
+    public void test_createFromRequest() {
+        ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
+                mBuilder.build());
+
+        assertThat(originalAdvertisement.getActions())
+                .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
+        assertThat(originalAdvertisement.getIdentity()).isEqualTo(IDENTITY);
+        assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
+        assertThat(originalAdvertisement.getLength()).isEqualTo(66);
+        assertThat(originalAdvertisement.getVersion()).isEqualTo(
+                BroadcastRequest.PRESENCE_VERSION_V1);
+        assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT);
+        assertThat(originalAdvertisement.getDataElements())
+                .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
+    }
+
+    @Test
+    public void test_createFromRequest_encodeAndDecode() {
+        ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
+                mBuilder.build());
+
+        byte[] generatedBytes = originalAdvertisement.toBytes();
+
+        ExtendedAdvertisement newAdvertisement =
+                ExtendedAdvertisement.fromBytes(generatedBytes, mPublicCredential);
+
+        assertThat(newAdvertisement.getActions())
+                .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
+        assertThat(newAdvertisement.getIdentity()).isEqualTo(IDENTITY);
+        assertThat(newAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
+        assertThat(newAdvertisement.getLength()).isEqualTo(66);
+        assertThat(newAdvertisement.getVersion()).isEqualTo(
+                BroadcastRequest.PRESENCE_VERSION_V1);
+        assertThat(newAdvertisement.getSalt()).isEqualTo(SALT);
+        assertThat(newAdvertisement.getDataElements())
+                .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
+    }
+
+    @Test
+    public void test_createFromRequest_invalidParameter() {
+        // invalid version
+        mBuilder.setVersion(BroadcastRequest.PRESENCE_VERSION_V0);
+        assertThat(ExtendedAdvertisement.createFromRequest(mBuilder.build())).isNull();
+
+        // invalid salt
+        PresenceBroadcastRequest.Builder builder =
+                new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
+                        new byte[]{1, 2, 3}, mPrivateCredential)
+                        .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
+                        .addAction(PRESENCE_ACTION_1);
+        assertThat(ExtendedAdvertisement.createFromRequest(builder.build())).isNull();
+
+        // invalid identity
+        PrivateCredential privateCredential =
+                new PrivateCredential.Builder(SECRET_ID,
+                        AUTHENTICITY_KEY, new byte[]{1, 2, 3, 4}, DEVICE_NAME)
+                        .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+                        .build();
+        PresenceBroadcastRequest.Builder builder2 =
+                new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
+                        new byte[]{1, 2, 3}, privateCredential)
+                        .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
+                        .addAction(PRESENCE_ACTION_1);
+        assertThat(ExtendedAdvertisement.createFromRequest(builder2.build())).isNull();
+
+        // empty action
+        PresenceBroadcastRequest.Builder builder3 =
+                new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
+                        SALT, mPrivateCredential)
+                        .setVersion(BroadcastRequest.PRESENCE_VERSION_V1);
+        assertThat(ExtendedAdvertisement.createFromRequest(builder3.build())).isNull();
+    }
+
+    @Test
+    public void test_toBytes() {
+        ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
+        assertThat(adv.toBytes()).isEqualTo(getExtendedAdvertisementByteArray());
+    }
+
+    @Test
+    public void test_fromBytes() {
+        byte[] originalBytes = getExtendedAdvertisementByteArray();
+        ExtendedAdvertisement adv =
+                ExtendedAdvertisement.fromBytes(originalBytes, mPublicCredential);
+
+        assertThat(adv.getActions())
+                .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
+        assertThat(adv.getIdentity()).isEqualTo(IDENTITY);
+        assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE);
+        assertThat(adv.getLength()).isEqualTo(66);
+        assertThat(adv.getVersion()).isEqualTo(
+                BroadcastRequest.PRESENCE_VERSION_V1);
+        assertThat(adv.getSalt()).isEqualTo(SALT);
+        assertThat(adv.getDataElements())
+                .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
+    }
+
+    @Test
+    public void test_toString() {
+        ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
+        assertThat(adv.toString()).isEqualTo("ExtendedAdvertisement:"
+                + "<VERSION: 1, length: 66, dataElementCount: 2, identityType: 1, "
+                + "identity: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], salt: [2, 3],"
+                + " actions: [1, 2]>");
+    }
+
+    @Test
+    public void test_getDataElements_accordingToType() {
+        ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
+        List<DataElement> dataElements = new ArrayList<>();
+
+        dataElements.add(BLE_ADDRESS_ELEMENT);
+        assertThat(adv.getDataElements(DATA_TYPE_BLE_ADDRESS)).isEqualTo(dataElements);
+        assertThat(adv.getDataElements(DATA_TYPE_PUBLIC_IDENTITY)).isEmpty();
+    }
+
+    private static byte[] getExtendedAdvertisementByteArray() {
+        ByteBuffer buffer = ByteBuffer.allocate(66);
+        buffer.put((byte) 0b00100000); // Header V1
+        buffer.put((byte) 0b00100000); // Salt header: length 2, type 0
+        // Salt data
+        buffer.put(SALT);
+        // Identity header: length 16, type 1 (private identity)
+        buffer.put(new byte[]{(byte) 0b10010000, (byte) 0b00000001});
+        // Identity data
+        buffer.put(CryptorImpIdentityV1.getInstance().encrypt(IDENTITY, SALT, AUTHENTICITY_KEY));
+
+        ByteBuffer deBuffer = ByteBuffer.allocate(28);
+        // Action1 header: length 1, type 6
+        deBuffer.put(new byte[]{(byte) 0b00010110});
+        // Action1 data
+        deBuffer.put((byte) PRESENCE_ACTION_1);
+        // Action2 header: length 1, type 6
+        deBuffer.put(new byte[]{(byte) 0b00010110});
+        // Action2 data
+        deBuffer.put((byte) PRESENCE_ACTION_2);
+        // Ble address header: length 7, type 102
+        deBuffer.put(new byte[]{(byte) 0b10000111, (byte) 0b01100101});
+        // Ble address data
+        deBuffer.put(BLE_ADDRESS);
+        // model id header: length 13, type 7
+        deBuffer.put(new byte[]{(byte) 0b10001101, (byte) 0b00000111});
+        // model id data
+        deBuffer.put(MODE_ID_DATA);
+
+        byte[] data = deBuffer.array();
+        CryptorImpV1 cryptor = CryptorImpV1.getInstance();
+        buffer.put(cryptor.encrypt(data, SALT, AUTHENTICITY_KEY));
+        buffer.put(cryptor.sign(data, AUTHENTICITY_KEY));
+
+        return buffer.array();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java
new file mode 100644
index 0000000..c4fccf7
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.BroadcastRequest;
+import android.nearby.DataElement;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit test for {@link ExtendedAdvertisementUtils}.
+ */
+public class ExtendedAdvertisementUtilsTest {
+    private static final byte[] ADVERTISEMENT1 = new byte[]{0b00100000, 12, 34, 78, 10};
+    private static final byte[] ADVERTISEMENT2 = new byte[]{0b00100000, 0b00100011, 34, 78,
+            (byte) 0b10010000, (byte) 0b00000100,
+            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+
+    private static final int DATA_TYPE_SALT = 3;
+    private static final int DATA_TYPE_PRIVATE_IDENTITY = 4;
+
+    @Test
+    public void test_constructHeader() {
+        assertThat(ExtendedAdvertisementUtils.constructHeader(1)).isEqualTo(0b100000);
+        assertThat(ExtendedAdvertisementUtils.constructHeader(0)).isEqualTo(0);
+        assertThat(ExtendedAdvertisementUtils.constructHeader(6)).isEqualTo((byte) 0b11000000);
+    }
+
+    @Test
+    public void test_getVersion() {
+        assertThat(ExtendedAdvertisementUtils.getVersion(ADVERTISEMENT1)).isEqualTo(1);
+        byte[] adv = new byte[]{(byte) 0b10111100, 9, 19, 90, 23};
+        assertThat(ExtendedAdvertisementUtils.getVersion(adv)).isEqualTo(5);
+        byte[] adv2 = new byte[]{(byte) 0b10011111, 9, 19, 90, 23};
+        assertThat(ExtendedAdvertisementUtils.getVersion(adv2)).isEqualTo(4);
+    }
+
+    @Test
+    public void test_getDataElementHeader_salt() {
+        byte[] saltHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(ADVERTISEMENT2, 1);
+        DataElementHeader header = DataElementHeader.fromBytes(
+                BroadcastRequest.PRESENCE_VERSION_V1, saltHeaderArray);
+        assertThat(header.getDataType()).isEqualTo(DATA_TYPE_SALT);
+        assertThat(header.getDataLength()).isEqualTo(ExtendedAdvertisement.SALT_DATA_LENGTH);
+    }
+
+    @Test
+    public void test_getDataElementHeader_identity() {
+        byte[] identityHeaderArray =
+                ExtendedAdvertisementUtils.getDataElementHeader(ADVERTISEMENT2, 4);
+        DataElementHeader header = DataElementHeader.fromBytes(BroadcastRequest.PRESENCE_VERSION_V1,
+                identityHeaderArray);
+        assertThat(header.getDataType()).isEqualTo(DATA_TYPE_PRIVATE_IDENTITY);
+        assertThat(header.getDataLength()).isEqualTo(ExtendedAdvertisement.IDENTITY_DATA_LENGTH);
+    }
+
+    @Test
+    public void test_constructDataElement_salt() {
+        DataElement salt = new DataElement(DATA_TYPE_SALT, new byte[]{13, 14});
+        byte[] saltArray = ExtendedAdvertisementUtils.convertDataElementToBytes(salt);
+        // Data length and salt header length.
+        assertThat(saltArray.length).isEqualTo(ExtendedAdvertisement.SALT_DATA_LENGTH + 1);
+        // Header
+        assertThat(saltArray[0]).isEqualTo((byte) 0b00100011);
+        // Data
+        assertThat(saltArray[1]).isEqualTo((byte) 13);
+        assertThat(saltArray[2]).isEqualTo((byte) 14);
+    }
+
+    @Test
+    public void test_constructDataElement_privateIdentity() {
+        byte[] identityData = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+        DataElement identity = new DataElement(DATA_TYPE_PRIVATE_IDENTITY, identityData);
+        byte[] identityArray = ExtendedAdvertisementUtils.convertDataElementToBytes(identity);
+        // Data length and identity header length.
+        assertThat(identityArray.length).isEqualTo(ExtendedAdvertisement.IDENTITY_DATA_LENGTH + 2);
+        // 1st header byte
+        assertThat(identityArray[0]).isEqualTo((byte) 0b10010000);
+        // 2st header byte
+        assertThat(identityArray[1]).isEqualTo((byte) 0b00000100);
+        // Data
+        assertThat(Arrays.copyOfRange(identityArray, 2, identityArray.length))
+                .isEqualTo(identityData);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
index 5e0ccbe..8e3e068 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
@@ -75,6 +75,15 @@
         assertThat(originalAdvertisement.getVersion()).isEqualTo(
                 BroadcastRequest.PRESENCE_VERSION_V0);
         assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT);
+        assertThat(originalAdvertisement.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(originalAdvertisement.toString())
+                .isEqualTo("FastAdvertisement:<VERSION: 0, length: 19,"
+                        + " ltvFieldCount: 4,"
+                        + " identityType: 1,"
+                        + " identity: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],"
+                        + " salt: [2, 3],"
+                        + " actions: [123],"
+                        + " txPower: 4");
     }
 
     @Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
index 39cab94..856c1a8 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.nearby.DataElement;
+import android.nearby.NearbyDeviceParcelable;
 import android.nearby.PresenceCredential;
 import android.nearby.PresenceDevice;
 import android.nearby.PresenceScanFilter;
@@ -28,12 +30,15 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link PresenceDiscoveryResult}.
  */
 public class PresenceDiscoveryResultTest {
+    private static final int DATA_TYPE_ACCOUNT_KEY = 9;
+    private static final int DATA_TYPE_INTENT = 6;
     private static final int PRESENCE_ACTION = 123;
     private static final int TX_POWER = -1;
     private static final int RSSI = -41;
@@ -43,6 +48,8 @@
     private static final byte[] PUBLIC_KEY = new byte[]{1, 1, 2, 2};
     private static final byte[] ENCRYPTED_METADATA = new byte[]{1, 2, 3, 4, 5};
     private static final byte[] METADATA_ENCRYPTION_KEY_TAG = new byte[]{1, 1, 3, 4, 5};
+    private static final byte[] META_DATA_ENCRYPTION_KEY =
+            new byte[] {-39, -55, 115, 78, -57, 40, 115, 0, -112, 86, -86, 7, -42, 68, 11, 12};
 
     private PresenceDiscoveryResult.Builder mBuilder;
     private PublicCredential mCredential;
@@ -59,18 +66,68 @@
                 .setSalt(SALT)
                 .setTxPower(TX_POWER)
                 .setRssi(RSSI)
+                .setEncryptedIdentityTag(METADATA_ENCRYPTION_KEY_TAG)
                 .addPresenceAction(PRESENCE_ACTION);
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testToDevice() {
-        PresenceDiscoveryResult discoveryResult = mBuilder.build();
-        PresenceDevice presenceDevice = discoveryResult.toPresenceDevice();
+    public void testFromDevice() {
+        NearbyDeviceParcelable.Builder builder = new NearbyDeviceParcelable.Builder();
+        builder.setTxPower(TX_POWER)
+                .setRssi(RSSI)
+                .setEncryptionKeyTag(METADATA_ENCRYPTION_KEY_TAG)
+                .setSalt(SALT)
+                .setPublicCredential(mCredential);
 
-        assertThat(presenceDevice.getRssi()).isEqualTo(RSSI);
-        assertThat(Arrays.equals(presenceDevice.getSalt(), SALT)).isTrue();
-        assertThat(Arrays.equals(presenceDevice.getSecretId(), SECRET_ID)).isTrue();
+        PresenceDiscoveryResult discoveryResult =
+                PresenceDiscoveryResult.fromDevice(builder.build());
+        PresenceScanFilter scanFilter = new PresenceScanFilter.Builder()
+                .setMaxPathLoss(80)
+                .addCredential(mCredential)
+                .build();
+
+        assertThat(discoveryResult.matches(scanFilter)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testFromDevice_presenceDeviceAvailable() {
+        NearbyDeviceParcelable.Builder builder = new NearbyDeviceParcelable.Builder();
+        PresenceDevice presenceDevice =
+                new PresenceDevice.Builder("123", SALT, SECRET_ID, META_DATA_ENCRYPTION_KEY)
+                        .addExtendedProperty(new DataElement(
+                                DATA_TYPE_INTENT, new byte[]{(byte) PRESENCE_ACTION}))
+                        .build();
+        builder.setTxPower(TX_POWER)
+                .setRssi(RSSI)
+                .setEncryptionKeyTag(METADATA_ENCRYPTION_KEY_TAG)
+                .setPresenceDevice(presenceDevice)
+                .setPublicCredential(mCredential);
+
+        PresenceDiscoveryResult discoveryResult =
+                PresenceDiscoveryResult.fromDevice(builder.build());
+        PresenceScanFilter scanFilter = new PresenceScanFilter.Builder()
+                .setMaxPathLoss(80)
+                .addPresenceAction(PRESENCE_ACTION)
+                .addCredential(mCredential)
+                .build();
+
+        assertThat(discoveryResult.matches(scanFilter)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAccountMatches() {
+        DataElement accountKey = new DataElement(DATA_TYPE_ACCOUNT_KEY, new byte[]{1, 2, 3, 4});
+        mBuilder.addExtendedProperties(List.of(accountKey));
+        PresenceDiscoveryResult discoveryResult = mBuilder.build();
+
+        List<DataElement> extendedProperties = new ArrayList<>();
+        extendedProperties.add(new DataElement(DATA_TYPE_ACCOUNT_KEY, new byte[]{1, 2, 3, 4}));
+        extendedProperties.add(new DataElement(DATA_TYPE_INTENT,
+                new byte[]{(byte) PRESENCE_ACTION}));
+        assertThat(discoveryResult.accountKeyMatches(extendedProperties)).isTrue();
     }
 
     @Test
@@ -86,4 +143,24 @@
         assertThat(discoveryResult.matches(scanFilter)).isTrue();
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_notMatches() {
+        PresenceDiscoveryResult.Builder builder = new PresenceDiscoveryResult.Builder()
+                .setPublicCredential(mCredential)
+                .setSalt(SALT)
+                .setTxPower(TX_POWER)
+                .setRssi(RSSI)
+                .setEncryptedIdentityTag(new byte[]{5, 4, 3, 2, 1})
+                .addPresenceAction(PRESENCE_ACTION);
+
+        PresenceScanFilter scanFilter = new PresenceScanFilter.Builder()
+                .setMaxPathLoss(80)
+                .addPresenceAction(PRESENCE_ACTION)
+                .addCredential(mCredential)
+                .build();
+
+        PresenceDiscoveryResult discoveryResult = builder.build();
+        assertThat(discoveryResult.matches(scanFilter)).isFalse();
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
new file mode 100644
index 0000000..9deb1eb
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.nearby.DataElement;
+import android.nearby.PresenceDevice;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class PresenceManagerTest {
+    private static final byte[] IDENTITY =
+            new byte[] {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4};
+    private static final byte[] SALT = {2, 3};
+    private static final byte[] SECRET_ID =
+            new byte[] {-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88};
+
+    @Mock private Context mContext;
+    private LocatorContextWrapper mLocatorContextWrapper;
+    private PresenceManager mPresenceManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mLocatorContextWrapper = new LocatorContextWrapper(mContext);
+        mPresenceManager = new PresenceManager(mLocatorContextWrapper);
+        when(mContext.getContentResolver())
+                .thenReturn(InstrumentationRegistry.getInstrumentation()
+                        .getContext().getContentResolver());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testInit() {
+        mPresenceManager.initiate();
+
+        verify(mContext, times(1)).registerReceiver(any(), any());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDeviceStatusUpdated() {
+        DataElement dataElement1 = new DataElement(1, new byte[] {1, 2});
+        DataElement dataElement2 = new DataElement(2, new byte[] {-1, -2, 3, 4, 5, 6, 7, 8, 9});
+
+        PresenceDevice presenceDevice =
+                new PresenceDevice.Builder(/* deviceId= */ "deviceId", SALT, SECRET_ID, IDENTITY)
+                        .addExtendedProperty(dataElement1)
+                        .addExtendedProperty(dataElement2)
+                        .build();
+
+        mPresenceManager.mScanCallback.onDiscovered(presenceDevice);
+        mPresenceManager.mScanCallback.onUpdated(presenceDevice);
+        mPresenceManager.mScanCallback.onLost(presenceDevice);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
index d06a785..05b556b 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.nearby.provider;
 
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -24,12 +25,14 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisingSetCallback;
 import android.content.Context;
+import android.hardware.location.ContextHubManager;
 import android.nearby.BroadcastCallback;
+import android.nearby.BroadcastRequest;
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.server.nearby.injector.ContextHubManagerAdapter;
 import com.android.server.nearby.injector.Injector;
 
 import com.google.common.util.concurrent.MoreExecutors;
@@ -59,9 +62,10 @@
     }
 
     @Test
-    public void testOnStatus_success() {
+    public void testOnStatus_success_fastAdv() {
         byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
-        mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
+        mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V0,
+                advertiseBytes, mBroadcastListener);
 
         AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
         mBleBroadcastProvider.onStartSuccess(settings);
@@ -69,15 +73,47 @@
     }
 
     @Test
-    public void testOnStatus_failure() {
+    public void testOnStatus_success_extendedAdv() {
         byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
-        mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
+        mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V1,
+                advertiseBytes, mBroadcastListener);
+
+        // advertising set can not be mocked, so we will allow nulls
+        mBleBroadcastProvider.mAdvertisingSetCallback.onAdvertisingSetStarted(null, -30,
+                AdvertisingSetCallback.ADVERTISE_SUCCESS);
+        verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
+    }
+
+    @Test
+    public void testOnStatus_failure_fastAdv() {
+        byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
+        mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V0,
+                advertiseBytes, mBroadcastListener);
 
         mBleBroadcastProvider.onStartFailure(BroadcastCallback.STATUS_FAILURE);
         verify(mBroadcastListener, times(1))
                 .onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
     }
 
+    @Test
+    public void testOnStatus_failure_extendedAdv() {
+        byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
+        mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V1,
+                advertiseBytes, mBroadcastListener);
+
+        // advertising set can not be mocked, so we will allow nulls
+        mBleBroadcastProvider.mAdvertisingSetCallback.onAdvertisingSetStarted(null, -30,
+                AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+        // Can be additional failure if the test device does not support LE Extended Advertising.
+        verify(mBroadcastListener, atLeastOnce())
+                .onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
+    }
+
+    @Test
+    public void testStop() {
+        mBleBroadcastProvider.stop();
+    }
+
     private static class TestInjector implements Injector {
 
         @Override
@@ -88,7 +124,7 @@
         }
 
         @Override
-        public ContextHubManagerAdapter getContextHubManagerAdapter() {
+        public ContextHubManager getContextHubManager() {
             return null;
         }
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
index 902cc33..ebb897e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
@@ -18,6 +18,8 @@
 
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -30,10 +32,13 @@
 import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
 import android.content.Context;
+import android.hardware.location.ContextHubManager;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanFilter;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.nearby.injector.ContextHubManagerAdapter;
 import com.android.server.nearby.injector.Injector;
 
 import org.junit.Before;
@@ -42,6 +47,8 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 public final class BleDiscoveryProviderTest {
 
@@ -70,6 +77,7 @@
         // Wait for callback to be invoked
         Thread.sleep(500);
         verify(mListener, times(1)).onNearbyDeviceDiscovered(any());
+        mBleDiscoveryProvider.getScanCallback().onScanFailed(1);
     }
 
     @Test
@@ -78,6 +86,22 @@
         mBleDiscoveryProvider.onStop();
     }
 
+    @Test
+    public void test_stopScan_filersReset() {
+        List<ScanFilter> filterList = new ArrayList<>();
+        filterList.add(getSanFilter());
+
+        mBleDiscoveryProvider.getController().setProviderScanFilters(filterList);
+        mBleDiscoveryProvider.onStart();
+        mBleDiscoveryProvider.onStop();
+        assertThat(mBleDiscoveryProvider.getFiltersLocked()).isNull();
+    }
+
+    @Test
+    public void testInvalidateScanMode() {
+        mBleDiscoveryProvider.invalidateScanMode();
+    }
+
     private class TestInjector implements Injector {
         @Override
         public BluetoothAdapter getBluetoothAdapter() {
@@ -85,7 +109,7 @@
         }
 
         @Override
-        public ContextHubManagerAdapter getContextHubManagerAdapter() {
+        public ContextHubManager getContextHubManager() {
             return null;
         }
 
@@ -125,4 +149,22 @@
             return null;
         }
     }
+
+    private static PresenceScanFilter getSanFilter() {
+        return new PresenceScanFilter.Builder()
+                .setMaxPathLoss(70)
+                .addCredential(getPublicCredential())
+                .addPresenceAction(124)
+                .build();
+    }
+
+    private static PublicCredential getPublicCredential() {
+        return new PublicCredential.Builder(
+                new byte[]{1, 2},
+                new byte[]{1, 2},
+                new byte[]{1, 2},
+                new byte[]{1, 2},
+                new byte[]{1, 2})
+                .build();
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
index d45d570..0179901 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
@@ -101,8 +101,13 @@
     @Test
     public void testStartAdvertising() {
         mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener);
-        verify(mBleBroadcastProvider).start(any(byte[].class), any(
-                BleBroadcastProvider.BroadcastListener.class));
+        verify(mBleBroadcastProvider).start(eq(BroadcastRequest.PRESENCE_VERSION_V0),
+                any(byte[].class), any(BleBroadcastProvider.BroadcastListener.class));
+    }
+
+    @Test
+    public void testStopAdvertising() {
+        mBroadcastProviderManager.stopBroadcast(mBroadcastListener);
     }
 
     @Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
index 1b29b52..58b8584 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
@@ -16,18 +16,30 @@
 
 package com.android.server.nearby.provider;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.server.nearby.NearbyConfiguration.NEARBY_MAINLINE_NANO_APP_MIN_VERSION;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.hardware.location.ContextHubClient;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
+import android.provider.DeviceConfig;
 
-import com.android.server.nearby.injector.ContextHubManagerAdapter;
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.server.nearby.injector.Injector;
 
 import org.junit.Before;
@@ -43,7 +55,8 @@
 
 public class ChreCommunicationTest {
     @Mock Injector mInjector;
-    @Mock ContextHubManagerAdapter mManager;
+    @Mock Context mContext;
+    @Mock ContextHubManager mManager;
     @Mock ContextHubTransaction<List<NanoAppState>> mTransaction;
     @Mock ContextHubTransaction.Response<List<NanoAppState>> mTransactionResponse;
     @Mock ContextHubClient mClient;
@@ -56,38 +69,63 @@
 
     @Before
     public void setUp() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_MAINLINE_NANO_APP_MIN_VERSION,
+                "1", false);
+
         MockitoAnnotations.initMocks(this);
-        when(mInjector.getContextHubManagerAdapter()).thenReturn(mManager);
+        when(mInjector.getContextHubManager()).thenReturn(mManager);
         when(mManager.getContextHubs()).thenReturn(Collections.singletonList(new ContextHubInfo()));
         when(mManager.queryNanoApps(any())).thenReturn(mTransaction);
-        when(mManager.createClient(any(), any(), any())).thenReturn(mClient);
+        when(mManager.createClient(any(), any(), any(), any())).thenReturn(mClient);
         when(mTransactionResponse.getResult()).thenReturn(ContextHubTransaction.RESULT_SUCCESS);
         when(mTransactionResponse.getContents())
                 .thenReturn(
                         Collections.singletonList(
                                 new NanoAppState(ChreDiscoveryProvider.NANOAPP_ID, 1, true)));
 
-        mChreCommunication = new ChreCommunication(mInjector, new InlineExecutor());
+        mChreCommunication = new ChreCommunication(mInjector, mContext, new InlineExecutor());
+    }
+
+    @Test
+    public void testStart() {
         mChreCommunication.start(
                 mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
 
         verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
         mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
-    }
-
-    @Test
-    public void testStart() {
         verify(mChreCallback).started(true);
     }
 
     @Test
     public void testStop() {
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
+
+        verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
+        mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
         mChreCommunication.stop();
         verify(mClient).close();
     }
 
     @Test
+    public void testNotReachMinVersion() {
+        DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_MAINLINE_NANO_APP_MIN_VERSION,
+                "3", false);
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
+        verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
+        mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
+        verify(mChreCallback).started(false);
+    }
+
+    @Test
     public void testSendMessageToNanApp() {
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
+        verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
+        mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
         NanoAppMessage message =
                 NanoAppMessage.createMessageToNanoApp(
                         ChreDiscoveryProvider.NANOAPP_ID,
@@ -99,6 +137,8 @@
 
     @Test
     public void testOnMessageFromNanoApp() {
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
         NanoAppMessage message =
                 NanoAppMessage.createMessageToNanoApp(
                         ChreDiscoveryProvider.NANOAPP_ID,
@@ -109,13 +149,60 @@
     }
 
     @Test
+    public void testContextHubTransactionResultToString() {
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_SUCCESS))
+                .isEqualTo("RESULT_SUCCESS");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_UNKNOWN))
+                .isEqualTo("RESULT_FAILED_UNKNOWN");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_BAD_PARAMS))
+                .isEqualTo("RESULT_FAILED_BAD_PARAMS");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_UNINITIALIZED))
+                .isEqualTo("RESULT_FAILED_UNINITIALIZED");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_BUSY))
+                .isEqualTo("RESULT_FAILED_BUSY");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_AT_HUB))
+                .isEqualTo("RESULT_FAILED_AT_HUB");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_TIMEOUT))
+                .isEqualTo("RESULT_FAILED_TIMEOUT");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE))
+                .isEqualTo("RESULT_FAILED_SERVICE_INTERNAL_FAILURE");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(
+                        ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE))
+                .isEqualTo("RESULT_FAILED_HAL_UNAVAILABLE");
+        assertThat(
+                mChreCommunication.contextHubTransactionResultToString(9))
+                .isEqualTo("UNKNOWN_RESULT value=9");
+    }
+
+    @Test
     public void testOnHubReset() {
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
         mChreCommunication.onHubReset(mClient);
         verify(mChreCallback).onHubReset();
     }
 
     @Test
     public void testOnNanoAppLoaded() {
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
         mChreCommunication.onNanoAppLoaded(mClient, ChreDiscoveryProvider.NANOAPP_ID);
         verify(mChreCallback).onNanoAppRestart(eq(ChreDiscoveryProvider.NANOAPP_ID));
     }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
index 7c0dd92..d06a447 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
@@ -16,16 +16,29 @@
 
 package com.android.server.nearby.provider;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.server.nearby.NearbyConfiguration.NEARBY_SUPPORT_TEST_APP;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.hardware.location.NanoAppMessage;
-import android.nearby.ScanFilter;
+import android.nearby.DataElement;
+import android.nearby.NearbyDeviceParcelable;
+import android.provider.DeviceConfig;
 
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.nearby.NearbyConfiguration;
+import com.android.server.nearby.presence.PresenceDiscoveryResult;
+
 import com.google.protobuf.ByteString;
 
 import org.junit.Before;
@@ -35,22 +48,39 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import service.proto.Blefilter;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
+import service.proto.Blefilter;
+
 public class ChreDiscoveryProviderTest {
     @Mock AbstractDiscoveryProvider.Listener mListener;
     @Mock ChreCommunication mChreCommunication;
 
     @Captor ArgumentCaptor<ChreCommunication.ContextHubCommsCallback> mChreCallbackCaptor;
+    @Captor ArgumentCaptor<NearbyDeviceParcelable> mNearbyDevice;
+
+    private static final int DATA_TYPE_CONNECTION_STATUS_KEY = 10;
+    private static final int DATA_TYPE_BATTERY_KEY = 11;
+    private static final int DATA_TYPE_TX_POWER_KEY = 5;
+    private static final int DATA_TYPE_BLUETOOTH_ADDR_KEY = 101;
+    private static final int DATA_TYPE_FP_ACCOUNT_KEY = 9;
+    private static final int DATA_TYPE_BLE_SERVICE_DATA_KEY = 100;
+    private static final int DATA_TYPE_TEST_1_KEY = 256;
+    private static final int DATA_TYPE_TEST_2_KEY = 257;
+    private static final int DATA_TYPE_TEST_3_KEY = 258;
+    private static final int DATA_TYPE_TEST_4_KEY = 259;
+    private static final int DATA_TYPE_TEST_5_KEY = 260;
 
     private ChreDiscoveryProvider mChreDiscoveryProvider;
 
+
     @Before
     public void setUp() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG);
+
         MockitoAnnotations.initMocks(this);
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         mChreDiscoveryProvider =
@@ -59,13 +89,10 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testOnStart() {
-        List<ScanFilter> scanFilters = new ArrayList<>();
-        mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
-        mChreDiscoveryProvider.onStart();
+    public void testInit() {
+        mChreDiscoveryProvider.init();
         verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
         mChreCallbackCaptor.getValue().started(true);
-        verify(mChreCommunication).sendMessageToNanoApp(any());
     }
 
     @Test
@@ -93,12 +120,288 @@
                         ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
                         results.toByteArray());
         mChreDiscoveryProvider.getController().setListener(mListener);
+        mChreDiscoveryProvider.init();
         mChreDiscoveryProvider.onStart();
         verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
         mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message);
         verify(mListener).onNearbyDeviceDiscovered(any());
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testOnNearbyDeviceDiscoveredWithDataElements() {
+        // Disables the setting of test app support
+        boolean isSupportedTestApp = getDeviceConfigBoolean(
+                NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);
+        if (isSupportedTestApp) {
+            DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_SUPPORT_TEST_APP, "false", false);
+        }
+        assertThat(new NearbyConfiguration().isTestAppSupported()).isFalse();
+
+        final byte [] connectionStatus = new byte[] {1, 2, 3};
+        final byte [] batteryStatus = new byte[] {4, 5, 6};
+        final byte [] txPower = new byte[] {2};
+        final byte [] bluetoothAddr = new byte[] {1, 2, 3, 4, 5, 6};
+        final byte [] fastPairAccountKey = new byte[16];
+        // First byte is length of service data, padding zeros should be thrown away.
+        final byte [] bleServiceData = new byte[] {5, 1, 2, 3, 4, 5, 0, 0, 0, 0};
+        final byte [] testData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+        final List<DataElement> expectedExtendedProperties = new ArrayList<>();
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_CONNECTION_STATUS_KEY,
+                connectionStatus));
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_BATTERY_KEY, batteryStatus));
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_TX_POWER_KEY, txPower));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_BLUETOOTH_ADDR_KEY, bluetoothAddr));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_FP_ACCOUNT_KEY, fastPairAccountKey));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_BLE_SERVICE_DATA_KEY, new byte[] {1, 2, 3, 4, 5}));
+
+        Blefilter.PublicCredential credential =
+                Blefilter.PublicCredential.newBuilder()
+                        .setSecretId(ByteString.copyFrom(new byte[] {1}))
+                        .setAuthenticityKey(ByteString.copyFrom(new byte[2]))
+                        .setPublicKey(ByteString.copyFrom(new byte[3]))
+                        .setEncryptedMetadata(ByteString.copyFrom(new byte[4]))
+                        .setEncryptedMetadataTag(ByteString.copyFrom(new byte[5]))
+                        .build();
+        Blefilter.BleFilterResult result =
+                Blefilter.BleFilterResult.newBuilder()
+                        .setTxPower(2)
+                        .setRssi(1)
+                        .setBluetoothAddress(ByteString.copyFrom(bluetoothAddr))
+                        .setBleServiceData(ByteString.copyFrom(bleServiceData))
+                        .setPublicCredential(credential)
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_CONNECTION_STATUS)
+                                .setValue(ByteString.copyFrom(connectionStatus))
+                                .setValueLength(connectionStatus.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_BATTERY_STATUS)
+                                .setValue(ByteString.copyFrom(batteryStatus))
+                                .setValueLength(batteryStatus.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_FAST_PAIR_ACCOUNT_KEY)
+                                .setValue(ByteString.copyFrom(fastPairAccountKey))
+                                .setValueLength(fastPairAccountKey.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_1)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_2)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_3)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_4)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_5)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .build();
+        Blefilter.BleFilterResults results =
+                Blefilter.BleFilterResults.newBuilder().addResult(result).build();
+        NanoAppMessage chre_message =
+                NanoAppMessage.createMessageToNanoApp(
+                        ChreDiscoveryProvider.NANOAPP_ID,
+                        ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
+                        results.toByteArray());
+        mChreDiscoveryProvider.getController().setListener(mListener);
+        mChreDiscoveryProvider.init();
+        mChreDiscoveryProvider.onStart();
+        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
+        mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message);
+        verify(mListener).onNearbyDeviceDiscovered(mNearbyDevice.capture());
+
+        List<DataElement> extendedProperties = PresenceDiscoveryResult
+                .fromDevice(mNearbyDevice.getValue()).getExtendedProperties();
+        assertThat(extendedProperties).containsExactlyElementsIn(expectedExtendedProperties);
+        // Reverts the setting of test app support
+        if (isSupportedTestApp) {
+            DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_SUPPORT_TEST_APP, "true", false);
+            assertThat(new NearbyConfiguration().isTestAppSupported()).isTrue();
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testOnNearbyDeviceDiscoveredWithTestDataElements() {
+        // Enables the setting of test app support
+        boolean isSupportedTestApp = getDeviceConfigBoolean(
+                NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);
+        if (!isSupportedTestApp) {
+            DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_SUPPORT_TEST_APP, "true", false);
+        }
+        assertThat(new NearbyConfiguration().isTestAppSupported()).isTrue();
+
+        final byte [] connectionStatus = new byte[] {1, 2, 3};
+        final byte [] batteryStatus = new byte[] {4, 5, 6};
+        final byte [] txPower = new byte[] {2};
+        final byte [] bluetoothAddr = new byte[] {1, 2, 3, 4, 5, 6};
+        final byte [] fastPairAccountKey = new byte[16];
+        // First byte is length of service data, padding zeros should be thrown away.
+        final byte [] bleServiceData = new byte[] {5, 1, 2, 3, 4, 5, 0, 0, 0, 0};
+        final byte [] testData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+        final List<DataElement> expectedExtendedProperties = new ArrayList<>();
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_CONNECTION_STATUS_KEY,
+                connectionStatus));
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_BATTERY_KEY, batteryStatus));
+        expectedExtendedProperties.add(new DataElement(DATA_TYPE_TX_POWER_KEY, txPower));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_BLUETOOTH_ADDR_KEY, bluetoothAddr));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_FP_ACCOUNT_KEY, fastPairAccountKey));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_BLE_SERVICE_DATA_KEY, new byte[] {1, 2, 3, 4, 5}));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_TEST_1_KEY, testData));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_TEST_2_KEY, testData));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_TEST_3_KEY, testData));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_TEST_4_KEY, testData));
+        expectedExtendedProperties.add(
+                new DataElement(DATA_TYPE_TEST_5_KEY, testData));
+
+        Blefilter.PublicCredential credential =
+                Blefilter.PublicCredential.newBuilder()
+                        .setSecretId(ByteString.copyFrom(new byte[] {1}))
+                        .setAuthenticityKey(ByteString.copyFrom(new byte[2]))
+                        .setPublicKey(ByteString.copyFrom(new byte[3]))
+                        .setEncryptedMetadata(ByteString.copyFrom(new byte[4]))
+                        .setEncryptedMetadataTag(ByteString.copyFrom(new byte[5]))
+                        .build();
+        Blefilter.BleFilterResult result =
+                Blefilter.BleFilterResult.newBuilder()
+                        .setTxPower(2)
+                        .setRssi(1)
+                        .setBluetoothAddress(ByteString.copyFrom(bluetoothAddr))
+                        .setBleServiceData(ByteString.copyFrom(bleServiceData))
+                        .setPublicCredential(credential)
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_CONNECTION_STATUS)
+                                .setValue(ByteString.copyFrom(connectionStatus))
+                                .setValueLength(connectionStatus.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_BATTERY_STATUS)
+                                .setValue(ByteString.copyFrom(batteryStatus))
+                                .setValueLength(batteryStatus.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_FAST_PAIR_ACCOUNT_KEY)
+                                .setValue(ByteString.copyFrom(fastPairAccountKey))
+                                .setValueLength(fastPairAccountKey.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_1)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_2)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_3)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_4)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .addDataElement(Blefilter.DataElement.newBuilder()
+                                .setKey(
+                                        Blefilter.DataElement.ElementType
+                                                .DE_TEST_5)
+                                .setValue(ByteString.copyFrom(testData))
+                                .setValueLength(testData.length)
+                        )
+                        .build();
+        Blefilter.BleFilterResults results =
+                Blefilter.BleFilterResults.newBuilder().addResult(result).build();
+        NanoAppMessage chre_message =
+                NanoAppMessage.createMessageToNanoApp(
+                        ChreDiscoveryProvider.NANOAPP_ID,
+                        ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
+                        results.toByteArray());
+        mChreDiscoveryProvider.getController().setListener(mListener);
+        mChreDiscoveryProvider.init();
+        mChreDiscoveryProvider.onStart();
+        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
+        mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message);
+        verify(mListener).onNearbyDeviceDiscovered(mNearbyDevice.capture());
+
+        List<DataElement> extendedProperties = PresenceDiscoveryResult
+                .fromDevice(mNearbyDevice.getValue()).getExtendedProperties();
+        assertThat(extendedProperties).containsExactlyElementsIn(expectedExtendedProperties);
+        // Reverts the setting of test app support
+        if (!isSupportedTestApp) {
+            DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_SUPPORT_TEST_APP, "false", false);
+            assertThat(new NearbyConfiguration().isTestAppSupported()).isFalse();
+        }
+    }
+
+    private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
+        final String value = getDeviceConfigProperty(name);
+        return value != null ? Boolean.parseBoolean(value) : defaultValue;
+    }
+
+    private String getDeviceConfigProperty(String name) {
+        return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TETHERING, name);
+    }
+
     private static class InLineExecutor implements Executor {
         @Override
         public void execute(Runnable command) {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
new file mode 100644
index 0000000..91a0b56
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.nearby.DataElement;
+import android.nearby.IScanListener;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanRequest;
+import android.os.IBinder;
+
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.util.identity.CallerIdentity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class DiscoveryProviderManagerTest {
+    private static final int SCAN_MODE_CHRE_ONLY = 3;
+    private static final int DATA_TYPE_SCAN_MODE = 102;
+    private static final int UID = 1234;
+    private static final int PID = 5678;
+    private static final String PACKAGE_NAME = "android.nearby.test";
+
+    @Mock Injector mInjector;
+    @Mock Context mContext;
+    @Mock AppOpsManager mAppOpsManager;
+    @Mock BleDiscoveryProvider mBleDiscoveryProvider;
+    @Mock ChreDiscoveryProvider mChreDiscoveryProvider;
+    @Mock DiscoveryProviderController mBluetoothController;
+    @Mock DiscoveryProviderController mChreController;
+    @Mock IScanListener mScanListener;
+    @Mock CallerIdentity mCallerIdentity;
+    @Mock DiscoveryProviderManager.ScanListenerDeathRecipient mScanListenerDeathRecipient;
+    @Mock IBinder mIBinder;
+
+    private DiscoveryProviderManager mDiscoveryProviderManager;
+    private Map<IBinder, DiscoveryProviderManager.ScanListenerRecord>
+            mScanTypeScanListenerRecordMap;
+
+    private static final int RSSI = -60;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
+        when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
+        when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
+
+        mScanTypeScanListenerRecordMap = new HashMap<>();
+        mDiscoveryProviderManager =
+                new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+                        mChreDiscoveryProvider,
+                        mScanTypeScanListenerRecordMap);
+        mCallerIdentity = CallerIdentity
+                .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
+    }
+
+    @Test
+    public void testOnNearbyDeviceDiscovered() {
+        NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .build();
+        mDiscoveryProviderManager.onNearbyDeviceDiscovered(nearbyDeviceParcelable);
+    }
+
+    @Test
+    public void testInvalidateProviderScanMode() {
+        mDiscoveryProviderManager.invalidateProviderScanMode();
+    }
+
+    @Test
+    public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getChreOnlyPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, never()).start();
+        assertThat(start).isTrue();
+    }
+
+    @Test
+    public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getChreOnlyPresenceScanFilter())
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, never()).start();
+        assertThat(start).isTrue();
+    }
+
+    @Test
+    public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(false);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getChreOnlyPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, never()).start();
+        assertThat(start).isFalse();
+    }
+
+    @Test
+    public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, never()).start();
+        assertThat(start).isTrue();
+    }
+
+    @Test
+    public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(false);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, atLeastOnce()).start();
+        assertThat(start).isTrue();
+    }
+
+    @Test
+    public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(null);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getChreOnlyPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, never()).start();
+        assertThat(start).isNull();
+    }
+
+    @Test
+    public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() {
+        when(mChreDiscoveryProvider.available()).thenReturn(null);
+
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+        Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
+        verify(mBluetoothController, atLeastOnce()).start();
+        assertThat(start).isTrue();
+    }
+
+    @Test
+    public void test_stopChreProvider_clearFilters() throws Exception {
+        // Cannot use mocked ChreDiscoveryProvider,
+        // so we cannot use class variable mDiscoveryProviderManager
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        DiscoveryProviderManager manager =
+                new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+                        new ChreDiscoveryProvider(
+                                mContext,
+                                new ChreCommunication(mInjector, mContext, executor), executor),
+                mScanTypeScanListenerRecordMap);
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+        manager.startChreProvider(List.of(getPresenceScanFilter()));
+        // This is an asynchronized process. The filters will be set in executor thread. So we need
+        // to wait for some time to get the correct result.
+        Thread.sleep(200);
+
+        assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+                .isTrue();
+        assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+        manager.stopChreProvider();
+        Thread.sleep(200);
+        // The filters should be cleared right after.
+        assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+                .isFalse();
+        assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNull();
+    }
+
+    @Test
+    public void test_restartChreProvider() throws Exception {
+        // Cannot use mocked ChreDiscoveryProvider,
+        // so we cannot use class variable mDiscoveryProviderManager
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        DiscoveryProviderManager manager =
+                new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+                        new ChreDiscoveryProvider(
+                                mContext,
+                                new ChreCommunication(mInjector, mContext, executor), executor),
+                        mScanTypeScanListenerRecordMap);
+        ScanRequest scanRequest = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .addScanFilter(getPresenceScanFilter()).build();
+        DiscoveryProviderManager.ScanListenerRecord record =
+                new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+                        mCallerIdentity, mScanListenerDeathRecipient);
+        mScanTypeScanListenerRecordMap.put(mIBinder, record);
+        manager.startChreProvider(List.of(getPresenceScanFilter()));
+        // This is an asynchronized process. The filters will be set in executor thread. So we need
+        // to wait for some time to get the correct result.
+        Thread.sleep(200);
+
+        assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+                .isTrue();
+        assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+        // We want to make sure quickly restart the provider the filters should
+        // be reset correctly.
+        // See b/255922206, there can be a race condition that filters get cleared because onStop()
+        // get executed after onStart() if they are called from different threads.
+        manager.stopChreProvider();
+        manager.mChreDiscoveryProvider.getController().setProviderScanFilters(
+                List.of(getPresenceScanFilter()));
+        manager.startChreProvider(List.of(getPresenceScanFilter()));
+        Thread.sleep(200);
+        assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+                .isTrue();
+        assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+        // Wait for enough time
+        Thread.sleep(1000);
+
+        assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+                .isTrue();
+        assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+    }
+
+    private static PresenceScanFilter getPresenceScanFilter() {
+        final byte[] secretId = new byte[]{1, 2, 3, 4};
+        final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
+        final byte[] publicKey = new byte[]{1, 1, 2, 2};
+        final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
+        final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
+
+        PublicCredential credential = new PublicCredential.Builder(
+                secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .build();
+
+        final int action = 123;
+        return new PresenceScanFilter.Builder()
+                .addCredential(credential)
+                .setMaxPathLoss(RSSI)
+                .addPresenceAction(action)
+                .build();
+    }
+
+    private static PresenceScanFilter getChreOnlyPresenceScanFilter() {
+        final byte[] secretId = new byte[]{1, 2, 3, 4};
+        final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
+        final byte[] publicKey = new byte[]{1, 1, 2, 2};
+        final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
+        final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
+
+        PublicCredential credential = new PublicCredential.Builder(
+                secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .build();
+
+        final int action = 123;
+        DataElement scanModeElement = new DataElement(DATA_TYPE_SCAN_MODE,
+                new byte[]{SCAN_MODE_CHRE_ONLY});
+        return new PresenceScanFilter.Builder()
+                .addCredential(credential)
+                .setMaxPathLoss(RSSI)
+                .addPresenceAction(action)
+                .addExtendedProperty(scanModeElement)
+                .build();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java
new file mode 100644
index 0000000..300efbd
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.nearby.FastPairDataProviderService;
+import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
+import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
+import android.nearby.aidl.FastPairManageAccountRequestParcel;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
+
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+import service.proto.FastPairString;
+import service.proto.Rpcs;
+
+public class FastPairDataProviderTest {
+
+    private static final Account ACCOUNT = new Account("abc@google.com", "type1");
+    private static final byte[] MODEL_ID = new byte[]{7, 9};
+    private static final int BLE_TX_POWER = 5;
+    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+    private static final int DEVICE_TYPE = 1;
+    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+    private static final byte[] IMAGE = new byte[]{7, 9};
+    private static final String IMAGE_URL = "IMAGE_URL";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+            "INITIAL_NOTIFICATION_DESCRIPTION";
+    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+    private static final String INTENT_URI = "INTENT_URI";
+    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+    private static final float TRIGGER_DISTANCE = 111;
+    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+            "UPDATE_COMPANION_APP_DESCRIPTION";
+    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+    private static final byte[] ACCOUNT_KEY = new byte[]{3};
+    private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[]{2, 8};
+    private static final byte[] ANTI_SPOOFING_KEY = new byte[]{4, 5, 6};
+    private static final String ACTION_URL = "ACTION_URL";
+    private static final String APP_NAME = "APP_NAME";
+    private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[]{5, 7};
+    private static final String DESCRIPTION = "DESCRIPTION";
+    private static final String DEVICE_NAME = "DEVICE_NAME";
+    private static final String DISPLAY_URL = "DISPLAY_URL";
+    private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
+    private static final String ICON_FIFE_URL = "ICON_FIFE_URL";
+    private static final byte[] ICON_PNG = new byte[]{2, 5};
+    private static final String ID = "ID";
+    private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
+    private static final String MAC_ADDRESS = "MAC_ADDRESS";
+    private static final String NAME = "NAME";
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+    private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+    private static final int RSSI = 9;
+    private static final String TITLE = "TITLE";
+    private static final String TRIGGER_ID = "TRIGGER_ID";
+    private static final int TX_POWER = 63;
+
+    @Mock ProxyFastPairDataProvider mProxyFastPairDataProvider;
+
+    FastPairDataProvider mFastPairDataProvider;
+    FastPairEligibleAccountParcel[] mFastPairEligibleAccountParcels =
+            { genHappyPathFastPairEligibleAccountParcel() };
+    FastPairAntispoofKeyDeviceMetadataParcel mFastPairAntispoofKeyDeviceMetadataParcel =
+            genHappyPathFastPairAntispoofKeyDeviceMetadataParcel();
+    FastPairUploadInfo mFastPairUploadInfo = genHappyPathFastPairUploadInfo();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mFastPairDataProvider = FastPairDataProvider.init(context);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testFailurePath_throwsException() throws IllegalStateException {
+        mFastPairDataProvider = FastPairDataProvider.getInstance();
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.loadFastPairEligibleAccounts(); });
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID); });
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT); });
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.loadFastPairDeviceWithAccountKey(
+                            ACCOUNT, ImmutableList.of()); });
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.optIn(ACCOUNT); });
+        assertThrows(
+                IllegalStateException.class,
+                () -> {
+                    mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo); });
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLoadFastPairAntispoofKeyDeviceMetadata_receivesResponse()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        when(mProxyFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any()))
+                .thenReturn(mFastPairAntispoofKeyDeviceMetadataParcel);
+
+        mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID);
+        ArgumentCaptor<FastPairAntispoofKeyDeviceMetadataRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataRequestParcel.class);
+        verify(mProxyFastPairDataProvider).loadFastPairAntispoofKeyDeviceMetadata(captor.capture());
+        assertThat(captor.getValue().modelId).isSameInstanceAs(MODEL_ID);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testOptIn_finishesSuccessfully()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        doNothing().when(mProxyFastPairDataProvider).manageFastPairAccount(any());
+        mFastPairDataProvider.optIn(ACCOUNT);
+        ArgumentCaptor<FastPairManageAccountRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairManageAccountRequestParcel.class);
+        verify(mProxyFastPairDataProvider).manageFastPairAccount(captor.capture());
+        assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+        assertThat(captor.getValue().requestType).isEqualTo(
+                FastPairDataProviderService.MANAGE_REQUEST_ADD);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testUpload_finishesSuccessfully()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        doNothing().when(mProxyFastPairDataProvider).manageFastPairAccountDevice(any());
+        mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo);
+        ArgumentCaptor<FastPairManageAccountDeviceRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairManageAccountDeviceRequestParcel.class);
+        verify(mProxyFastPairDataProvider).manageFastPairAccountDevice(captor.capture());
+        assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+        assertThat(captor.getValue().requestType).isEqualTo(
+                FastPairDataProviderService.MANAGE_REQUEST_ADD);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLoadFastPairEligibleAccounts_receivesOneAccount()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        when(mProxyFastPairDataProvider.loadFastPairEligibleAccounts(any()))
+                .thenReturn(mFastPairEligibleAccountParcels);
+        assertThat(mFastPairDataProvider.loadFastPairEligibleAccounts().size())
+                .isEqualTo(1);
+        ArgumentCaptor<FastPairEligibleAccountsRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairEligibleAccountsRequestParcel.class);
+        verify(mProxyFastPairDataProvider).loadFastPairEligibleAccounts(captor.capture());
+        assertThat(captor.getValue()).isNotNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLoadFastPairDeviceWithAccountKey_finishesSuccessfully()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any()))
+                .thenReturn(null);
+
+        mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT);
+        ArgumentCaptor<FastPairAccountDevicesMetadataRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class);
+        verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture());
+        assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+        assertThat(captor.getValue().deviceAccountKeys).isEmpty();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLoadFastPairDeviceWithAccountKeyDeviceAccountKeys_finishesSuccessfully()  {
+        mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+        when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any()))
+                .thenReturn(null);
+
+        mFastPairDataProvider.loadFastPairDeviceWithAccountKey(
+                ACCOUNT, ImmutableList.of(ACCOUNT_KEY));
+        ArgumentCaptor<FastPairAccountDevicesMetadataRequestParcel> captor =
+                ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class);
+        verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture());
+        assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+        assertThat(captor.getValue().deviceAccountKeys.length).isEqualTo(1);
+        assertThat(captor.getValue().deviceAccountKeys[0].byteArray).isSameInstanceAs(ACCOUNT_KEY);
+    }
+
+    private static FastPairEligibleAccountParcel genHappyPathFastPairEligibleAccountParcel() {
+        FastPairEligibleAccountParcel parcel = new FastPairEligibleAccountParcel();
+        parcel.account = ACCOUNT;
+        parcel.optIn = true;
+
+        return parcel;
+    }
+
+    private static FastPairAntispoofKeyDeviceMetadataParcel
+                genHappyPathFastPairAntispoofKeyDeviceMetadataParcel() {
+        FastPairAntispoofKeyDeviceMetadataParcel parcel =
+                new FastPairAntispoofKeyDeviceMetadataParcel();
+        parcel.antispoofPublicKey = ANTI_SPOOFING_KEY;
+        parcel.deviceMetadata = genHappyPathFastPairDeviceMetadataParcel();
+
+        return parcel;
+    }
+
+    private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+        FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+        parcel.bleTxPower = BLE_TX_POWER;
+        parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
+        parcel.connectSuccessCompanionAppNotInstalled =
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
+        parcel.deviceType = DEVICE_TYPE;
+        parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
+        parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
+        parcel.image = IMAGE;
+        parcel.imageUrl = IMAGE_URL;
+        parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
+        parcel.initialNotificationDescriptionNoAccount =
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
+        parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
+        parcel.intentUri = INTENT_URI;
+        parcel.name = NAME;
+        parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+        parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+        parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+        parcel.triggerDistance = TRIGGER_DISTANCE;
+        parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
+        parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
+        parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
+        parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
+        parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
+        parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
+        parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
+
+        return parcel;
+    }
+
+    private static Cache.StoredDiscoveryItem genHappyPathStoredDiscoveryItem() {
+        Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder =
+                Cache.StoredDiscoveryItem.newBuilder();
+        storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
+        storedDiscoveryItemBuilder.setActionUrlType(Cache.ResolvedUrlType.WEBPAGE);
+        storedDiscoveryItemBuilder.setAppName(APP_NAME);
+        storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
+                ByteString.copyFrom(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1));
+        storedDiscoveryItemBuilder.setDescription(DESCRIPTION);
+        storedDiscoveryItemBuilder.setDeviceName(DEVICE_NAME);
+        storedDiscoveryItemBuilder.setDisplayUrl(DISPLAY_URL);
+        storedDiscoveryItemBuilder.setFirstObservationTimestampMillis(
+                FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
+        storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
+        storedDiscoveryItemBuilder.setId(ID);
+        storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
+                LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        storedDiscoveryItemBuilder.setMacAddress(MAC_ADDRESS);
+        storedDiscoveryItemBuilder.setPackageName(PACKAGE_NAME);
+        storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis(
+                PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+        storedDiscoveryItemBuilder.setRssi(RSSI);
+        storedDiscoveryItemBuilder.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+        storedDiscoveryItemBuilder.setTitle(TITLE);
+        storedDiscoveryItemBuilder.setTriggerId(TRIGGER_ID);
+        storedDiscoveryItemBuilder.setTxPower(TX_POWER);
+
+        FastPairString.FastPairStrings.Builder stringsBuilder =
+                FastPairString.FastPairStrings.newBuilder();
+        stringsBuilder.setPairingFinishedCompanionAppInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        stringsBuilder.setPairingFinishedCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        stringsBuilder.setPairingFailDescription(
+                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        stringsBuilder.setTapToPairWithAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION);
+        stringsBuilder.setTapToPairWithoutAccount(
+                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+        stringsBuilder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+        stringsBuilder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+        stringsBuilder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+        stringsBuilder.setWaitAppLaunchDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+        storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build());
+
+        Cache.FastPairInformation.Builder fpInformationBuilder =
+                Cache.FastPairInformation.newBuilder();
+        Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
+                Rpcs.TrueWirelessHeadsetImages.newBuilder();
+        imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE);
+        imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build());
+        fpInformationBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES);
+
+        storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build());
+        storedDiscoveryItemBuilder.setTxPower(TX_POWER);
+
+        storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
+        storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
+        storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
+
+        return storedDiscoveryItemBuilder.build();
+    }
+
+    private static FastPairUploadInfo genHappyPathFastPairUploadInfo() {
+        return new FastPairUploadInfo(
+                genHappyPathStoredDiscoveryItem(),
+                ByteString.copyFrom(ACCOUNT_KEY),
+                ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
index eeea319..35f87f1 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
@@ -110,8 +110,6 @@
     private static final byte[] ICON_PNG = new byte[]{2, 5};
     private static final String ID = "ID";
     private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
-    private static final int LAST_USER_EXPERIENCE = 1;
-    private static final long LOST_MILLIS = 393284L;
     private static final String MAC_ADDRESS = "MAC_ADDRESS";
     private static final String NAME = "NAME";
     private static final String PACKAGE_NAME = "PACKAGE_NAME";
@@ -121,7 +119,6 @@
     private static final String TITLE = "TITLE";
     private static final String TRIGGER_ID = "TRIGGER_ID";
     private static final int TX_POWER = 63;
-    private static final int TYPE = 1;
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java
new file mode 100644
index 0000000..a759baf
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+
+public final class ArrayUtilsTest {
+
+    private static final byte[] BYTES_ONE = new byte[] {7, 9};
+    private static final byte[] BYTES_TWO = new byte[] {8};
+    private static final byte[] BYTES_EMPTY = new byte[] {};
+    private static final byte[] BYTES_ALL = new byte[] {7, 9, 8};
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConcatByteArraysNoInput() {
+        assertThat(ArrayUtils.concatByteArrays().length).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConcatByteArraysOneEmptyArray() {
+        assertThat(ArrayUtils.concatByteArrays(BYTES_EMPTY).length).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConcatByteArraysOneNonEmptyArray() {
+        assertThat(ArrayUtils.concatByteArrays(BYTES_ONE)).isEqualTo(BYTES_ONE);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConcatByteArraysMultipleNonEmptyArrays() {
+        assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_TWO)).isEqualTo(BYTES_ALL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConcatByteArraysMultipleArrays() {
+        assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_EMPTY, BYTES_TWO))
+                .isEqualTo(BYTES_ALL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsEmptyNull_returnsTrue() {
+        assertThat(ArrayUtils.isEmpty(null)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsEmpty_returnsTrue() {
+        assertThat(ArrayUtils.isEmpty(new byte[]{})).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsEmpty_returnsFalse() {
+        assertThat(ArrayUtils.isEmpty(BYTES_ALL)).isFalse();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
index 1a22412..71ade2a 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
@@ -93,4 +93,9 @@
         assertThat(BroadcastPermissions.getPermissionLevel(mMockContext, UID, PID))
                 .isEqualTo(PERMISSION_BLUETOOTH_ADVERTISE);
     }
+
+    @Test
+    public void test_enforceBroadcastPermission() {
+        BroadcastPermissions.enforceBroadcastPermission(mMockContext, mCallerIdentity);
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
index f098600..ac90b9f 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
@@ -30,6 +30,7 @@
 
 public final class DataUtilsTest {
     private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+    private static final String MODEL_ID = "model_id";
     private static final String APP_PACKAGE = "test_package";
     private static final String APP_ACTION_URL =
             "intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
@@ -57,15 +58,11 @@
     private static final String MESSAGE_RETROACTIVE_PAIR_DESCRIPTION = "message 7";
     private static final String MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = "message 8";
     private static final String MESSAGE_FAIL_CONNECT_DESCRIPTION = "message 9";
-    private static final String MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
-            "message 10";
-    private static final String MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION = "message 11";
-    private static final String MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION = "message 12";
 
     @Test
     public void test_toScanFastPairStoreItem_withAccount() {
         Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, ACCOUNT);
         assertThat(item.getAddress()).isEqualTo(BLUETOOTH_ADDRESS);
         assertThat(item.getActionUrl()).isEqualTo(APP_ACTION_URL);
         assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
@@ -97,7 +94,7 @@
     @Test
     public void test_toScanFastPairStoreItem_withoutAccount() {
         Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, /* account= */ null);
+                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, /* account= */ null);
         FastPairStrings strings = item.getFastPairStrings();
         assertThat(strings.getInitialPairingDescription())
                 .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
@@ -106,7 +103,25 @@
     @Test
     public void test_toString() {
         Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, ACCOUNT);
+
+        assertThat(DataUtils.toString(item))
+                .isEqualTo("ScanFastPairStoreItem=[address:00:11:22:33:FF:EE, "
+                        + "actionUrl:intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
+                        + "package=to_be_set;component=to_be_set;"
+                        + "to_be_set%3AEXTRA_COMPANION_APP=test_package;"
+                        + "end, deviceName:My device, "
+                        + "iconFifeUrl:device_image_url, "
+                        + "fastPairStrings:FastPairStrings[tapToPairWithAccount=message 1, "
+                        + "tapToPairWithoutAccount=message 2, "
+                        + "initialPairingDescription=message 3 My device, "
+                        + "pairingFinishedCompanionAppInstalled=message 4, "
+                        + "pairingFinishedCompanionAppNotInstalled=message 5, "
+                        + "subsequentPairingDescription=message 6, "
+                        + "retroactivePairingDescription=message 7, "
+                        + "waitAppLaunchDescription=message 8, "
+                        + "pairingFailDescription=message 9]]");
+
         FastPairStrings strings = item.getFastPairStrings();
 
         assertThat(DataUtils.toString(strings))
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java
new file mode 100644
index 0000000..e167cf4
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import org.junit.Test;
+
+public class EnvironmentTest {
+
+    @Test
+    public void getNearbyDirectory() {
+        Environment.getNearbyDirectory();
+        Environment.getNearbyDirectory(1);
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java
new file mode 100644
index 0000000..f0294fc
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class CryptorImpIdentityV1Test {
+    private static final String TAG = "CryptorImpIdentityV1Test";
+    private static final byte[] SALT = new byte[] {102, 22};
+    private static final byte[] DATA =
+            new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
+    private static final byte[] AUTHENTICITY_KEY =
+            new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
+
+    @Test
+    public void test_encrypt_decrypt() {
+        Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
+        byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
+
+        assertThat(identityCryptor.decrypt(encryptedData, SALT, AUTHENTICITY_KEY)).isEqualTo(DATA);
+    }
+
+    @Test
+    public void test_encryption() {
+        Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
+        byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
+
+        // for debugging
+        Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData));
+
+        assertThat(encryptedData).isEqualTo(getEncryptedData());
+    }
+
+    @Test
+    public void test_decryption() {
+        Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
+        byte[] decryptedData =
+                identityCryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY);
+        // for debugging
+        Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData));
+
+        assertThat(decryptedData).isEqualTo(DATA);
+    }
+
+    @Test
+    public void generateHmacTag() {
+        CryptorImpIdentityV1 identityCryptor = CryptorImpIdentityV1.getInstance();
+        byte[] generatedTag = identityCryptor.sign(DATA);
+        byte[] expectedTag = new byte[]{50, 116, 95, -87, 63, 123, -79, -43};
+        assertThat(generatedTag).isEqualTo(expectedTag);
+    }
+
+    private static byte[] getEncryptedData() {
+        return new byte[]{6, -31, -32, -123, 43, -92, -47, -110, -65, 126, -15, -51, -19, -43};
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java
new file mode 100644
index 0000000..3ca2575
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit test for {@link CryptorImpV1}
+ */
+public final class CryptorImpV1Test {
+    private static final String TAG = "CryptorImpV1Test";
+    private static final byte[] SALT = new byte[] {102, 22};
+    private static final byte[] DATA =
+            new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
+    private static final byte[] AUTHENTICITY_KEY =
+            new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
+
+    @Test
+    public void test_encryption() {
+        Cryptor v1Cryptor = CryptorImpV1.getInstance();
+        byte[] encryptedData = v1Cryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
+
+        // for debugging
+        Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData));
+
+        assertThat(encryptedData).isEqualTo(getEncryptedData());
+    }
+
+    @Test
+    public void test_encryption_invalidInput() {
+        Cryptor v1Cryptor = CryptorImpV1.getInstance();
+        assertThat(v1Cryptor.encrypt(DATA, SALT, new byte[]{1, 2, 3, 4, 6})).isNull();
+    }
+
+    @Test
+    public void test_decryption() {
+        Cryptor v1Cryptor = CryptorImpV1.getInstance();
+        byte[] decryptedData =
+                v1Cryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY);
+        // for debugging
+        Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData));
+
+        assertThat(decryptedData).isEqualTo(DATA);
+    }
+
+    @Test
+    public void test_decryption_invalidInput() {
+        Cryptor v1Cryptor = CryptorImpV1.getInstance();
+        assertThat(v1Cryptor.decrypt(getEncryptedData(), SALT, new byte[]{1, 2, 3, 4, 6})).isNull();
+    }
+
+    @Test
+    public void generateSign() {
+        CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
+        byte[] generatedTag = v1Cryptor.sign(DATA, AUTHENTICITY_KEY);
+        byte[] expectedTag = new byte[]{
+                100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12};
+        assertThat(generatedTag).isEqualTo(expectedTag);
+    }
+
+    @Test
+    public void test_verify() {
+        CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
+        byte[] expectedTag = new byte[]{
+                100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12};
+
+        assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, expectedTag)).isTrue();
+        assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, DATA)).isFalse();
+    }
+
+    @Test
+    public void test_generateHmacTag_sameResult() {
+        CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
+        byte[] res1 = v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY);
+        assertThat(res1)
+                .isEqualTo(v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY));
+    }
+
+    @Test
+    public void test_generateHmacTag_nullData() {
+        CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
+        assertThat(v1Cryptor.generateHmacTag(/* data= */ null, AUTHENTICITY_KEY)).isNull();
+    }
+
+    @Test
+    public void test_generateHmacTag_nullKey() {
+        CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
+        assertThat(v1Cryptor.generateHmacTag(DATA, /* authenticityKey= */ null)).isNull();
+    }
+
+    private static byte[] getEncryptedData() {
+        return new byte[]{-92, 94, -99, -97, 81, -48, -7, 119, -64, -22, 45, -49, -50, 92};
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java
new file mode 100644
index 0000000..ca612e3
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link Cryptor}
+ */
+public final class CryptorTest {
+
+    private static final byte[] DATA =
+            new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
+    private static final byte[] AUTHENTICITY_KEY =
+            new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
+
+    @Test
+    public void test_computeHkdf() {
+        int outputSize = 16;
+        byte[] res1 = Cryptor.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize);
+        byte[] res2 = Cryptor.computeHkdf(DATA,
+                new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26},
+                outputSize);
+
+        assertThat(res1).hasLength(outputSize);
+        assertThat(res2).hasLength(outputSize);
+        assertThat(res1).isNotEqualTo(res2);
+        assertThat(res1)
+                .isEqualTo(CryptorImpV1.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize));
+    }
+
+    @Test
+    public void test_computeHkdf_invalidInput() {
+        assertThat(Cryptor.computeHkdf(DATA, AUTHENTICITY_KEY, /* size= */ 256000))
+                .isNull();
+        assertThat(Cryptor.computeHkdf(DATA, new byte[0], /* size= */ 255))
+                .isNull();
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java
new file mode 100644
index 0000000..c29cb92
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.util.identity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class CallerIdentityTest {
+    private static final int UID = 100;
+    private static final int PID = 10002;
+    private static final String PACKAGE_NAME = "package_name";
+    private static final String ATTRIBUTION_TAG = "attribution_tag";
+
+    @Test
+    public void testToString() {
+        CallerIdentity callerIdentity =
+                CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG);
+        assertThat(callerIdentity.toString()).isEqualTo("100/package_name[attribution_tag]");
+        assertThat(callerIdentity.isSystemServer()).isFalse();
+    }
+
+    @Test
+    public void testHashCode() {
+        CallerIdentity callerIdentity =
+                CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG);
+        CallerIdentity callerIdentity1 =
+                CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG);
+        assertThat(callerIdentity.hashCode()).isEqualTo(callerIdentity1.hashCode());
+    }
+}
diff --git a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
index c490cf8..9827f4e 100644
--- a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
@@ -17,20 +17,20 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System connectivity resources"</string>
-    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to a Wi-Fi network"</string>
+    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
-    <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+    <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
-    <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
-    <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no Internet access"</string>
+    <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no internet access"</string>
+    <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no internet access"</string>
     <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
     <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
     <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
     <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
-    <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+    <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
     <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"mobile data"</item>
diff --git a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
index fdca468..b24dee0 100644
--- a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
@@ -22,7 +22,7 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Acceder a la red"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
-    <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>no tiene acceso a Internet"</string>
+    <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Presiona para ver opciones"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
     <string name="other_networks_no_internet" msgid="5693932964749676542">"La red no tiene acceso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index 2c4e431..13f9eb4 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -35,8 +35,8 @@
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"datu-konexioa"</item>
     <item msgid="5624324321165953608">"Wifia"</item>
-    <item msgid="5667906231066981731">"Bluetooth-a"</item>
-    <item msgid="346574747471703768">"Ethernet-a"</item>
+    <item msgid="5667906231066981731">"Bluetootha"</item>
+    <item msgid="346574747471703768">"Etherneta"</item>
     <item msgid="5734728378097476003">"VPNa"</item>
   </string-array>
     <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"sare mota ezezaguna"</string>
diff --git a/service/ServiceConnectivityResources/res/values-kk/strings.xml b/service/ServiceConnectivityResources/res/values-kk/strings.xml
index 00c0f39..efe23b6 100644
--- a/service/ServiceConnectivityResources/res/values-kk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kk/strings.xml
@@ -33,7 +33,7 @@
     <string name="network_switch_metered_detail" msgid="1257300152739542096">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string>
     <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string>
   <string-array name="network_switch_type_name">
-    <item msgid="3004933964374161223">"мобильдік деректер"</item>
+    <item msgid="3004933964374161223">"мобильдік интернет"</item>
     <item msgid="5624324321165953608">"Wi-Fi"</item>
     <item msgid="5667906231066981731">"Bluetooth"</item>
     <item msgid="346574747471703768">"Ethernet"</item>
diff --git a/service/ServiceConnectivityResources/res/values-nb/strings.xml b/service/ServiceConnectivityResources/res/values-nb/strings.xml
index 00a0728..fff6530 100644
--- a/service/ServiceConnectivityResources/res/values-nb/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nb/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ressurser for systemtilkobling"</string>
-    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Logg på Wi-Fi-nettverket"</string>
+    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Logg på Wifi-nettverket"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"Logg på nettverk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
@@ -34,7 +34,7 @@
     <string name="network_switch_metered_toast" msgid="70691146054130335">"Byttet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"mobildata"</item>
-    <item msgid="5624324321165953608">"Wi-Fi"</item>
+    <item msgid="5624324321165953608">"Wifi"</item>
     <item msgid="5667906231066981731">"Bluetooth"</item>
     <item msgid="346574747471703768">"Ethernet"</item>
     <item msgid="5734728378097476003">"VPN"</item>
diff --git a/service/ServiceConnectivityResources/res/values-or/strings.xml b/service/ServiceConnectivityResources/res/values-or/strings.xml
index 8b85884..49a773a 100644
--- a/service/ServiceConnectivityResources/res/values-or/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-or/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ସିଷ୍ଟମର ସଂଯୋଗ ସମ୍ବନ୍ଧିତ ରିସୋର୍ସଗୁଡ଼ିକ"</string>
+    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ସିଷ୍ଟମ କନେକ୍ଟିଭିଟୀ ରିସୋର୍ସ"</string>
     <string name="wifi_available_sign_in" msgid="8041178343789805553">"ୱାଇ-ଫାଇ ନେଟୱର୍କରେ ସାଇନ୍‍-ଇନ୍‍ କରନ୍ତୁ"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"ନେଟ୍‌ୱର୍କରେ ସାଇନ୍‍ ଇନ୍‍ କରନ୍ତୁ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
diff --git a/service/ServiceConnectivityResources/res/values-ro/strings.xml b/service/ServiceConnectivityResources/res/values-ro/strings.xml
index fa5848f..bf4479a 100644
--- a/service/ServiceConnectivityResources/res/values-ro/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ro/strings.xml
@@ -18,17 +18,17 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resurse pentru conectivitatea sistemului"</string>
-    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Conectați-vă la rețeaua Wi-Fi"</string>
-    <string name="network_available_sign_in" msgid="2622520134876355561">"Conectați-vă la rețea"</string>
+    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Conectează-te la rețeaua Wi-Fi"</string>
+    <string name="network_available_sign_in" msgid="2622520134876355561">"Conectează-te la rețea"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
-    <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atingeți pentru opțiuni"</string>
+    <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atinge pentru opțiuni"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Rețeaua mobilă nu are acces la internet"</string>
     <string name="other_networks_no_internet" msgid="5693932964749676542">"Rețeaua nu are acces la internet"</string>
     <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Serverul DNS privat nu poate fi accesat"</string>
     <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> are conectivitate limitată"</string>
-    <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Atingeți pentru a vă conecta oricum"</string>
+    <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Atinge pentru a te conecta oricum"</string>
     <string name="network_switch_metered" msgid="5016937523571166319">"S-a comutat la <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
     <string name="network_switch_metered_detail" msgid="1257300152739542096">"Dispozitivul folosește <xliff:g id="NEW_NETWORK">%1$s</xliff:g> când <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nu are acces la internet. Se pot aplica taxe."</string>
     <string name="network_switch_metered_toast" msgid="70691146054130335">"S-a comutat de la <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> la <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml
index 385c75c..85bd84f 100644
--- a/service/ServiceConnectivityResources/res/values-sq/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml
@@ -35,7 +35,7 @@
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"të dhënat celulare"</item>
     <item msgid="5624324321165953608">"Wi-Fi"</item>
-    <item msgid="5667906231066981731">"Bluetooth"</item>
+    <item msgid="5667906231066981731">"Bluetooth-i"</item>
     <item msgid="346574747471703768">"Eternet"</item>
     <item msgid="5734728378097476003">"VPN"</item>
   </string-array>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 330a1da..17b8677 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -107,6 +107,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -3038,6 +3039,8 @@
         sendStickyBroadcast(makeGeneralIntent(info, bcastType));
     }
 
+    // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
+    @SuppressLint("NewApi")
     // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
     @TargetApi(Build.VERSION_CODES.S)
     private void sendStickyBroadcast(Intent intent) {
@@ -8516,6 +8519,8 @@
         // else not handled
     }
 
+    // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
+    @SuppressLint("NewApi")
     private void sendIntent(PendingIntent pendingIntent, Intent intent) {
         mPendingIntentWakeLock.acquire();
         try {
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index ff7240d..2c2d957 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@@ -45,7 +46,11 @@
         <service android:name=".MyService"
              android:exported="true"/>
         <service android:name=".MyForegroundService"
-             android:exported="true"/>
+             android:foregroundServiceType="specialUse"
+             android:exported="true">
+            <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" 
+                      android:value="Connectivity" />
+        </service>
         <service android:name=".RemoteSocketFactoryService"
              android:exported="true"/>