Merge "Update the commands to build and install the mainline module."
diff --git a/nearby/framework/java/android/nearby/BroadcastCallback.java b/nearby/framework/java/android/nearby/BroadcastCallback.java
new file mode 100644
index 0000000..54c1916
--- /dev/null
+++ b/nearby/framework/java/android/nearby/BroadcastCallback.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 android.nearby;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback when broadcasting request using nearby specification.
+ *
+ * @hide
+ */
+@SystemApi
+public interface BroadcastCallback {
+    /** Broadcast was successful. */
+    int STATUS_OK = 0;
+
+    /** General status code when broadcast failed. */
+    int STATUS_FAILURE = 1;
+
+    /**
+     * Broadcast failed as the callback was already registered.
+     */
+    int STATUS_FAILURE_ALREADY_REGISTERED = 2;
+
+    /**
+     * Broadcast failed as the request contains excessive data.
+     */
+    int STATUS_FAILURE_SIZE_EXCEED_LIMIT = 3;
+
+    /**
+     * Broadcast failed as the client doesn't hold required permissions.
+     */
+    int STATUS_FAILURE_MISSING_PERMISSIONS = 4;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATUS_OK, STATUS_FAILURE, STATUS_FAILURE_ALREADY_REGISTERED,
+            STATUS_FAILURE_SIZE_EXCEED_LIMIT, STATUS_FAILURE_MISSING_PERMISSIONS})
+    @interface BroadcastStatus {
+    }
+
+    /**
+     * Called when broadcast status changes.
+     */
+    void onStatus(@BroadcastStatus int status);
+}
diff --git a/nearby/framework/java/android/nearby/BroadcastRequest.java b/nearby/framework/java/android/nearby/BroadcastRequest.java
new file mode 100644
index 0000000..27468dd
--- /dev/null
+++ b/nearby/framework/java/android/nearby/BroadcastRequest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a {@link BroadcastRequest}.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("ParcelNotFinal")  // BroadcastRequest constructor is not public
+public abstract class BroadcastRequest implements Parcelable {
+
+    /** Broadcast type for advertising using nearby presence protocol. */
+    public static final int BROADCAST_TYPE_NEARBY_PRESENCE = 3;
+
+    /** @hide **/
+    // Currently, only Nearby Presence broadcast is supported, in the future
+    // broadcasting using other nearby specifications will be added.
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BROADCAST_TYPE_NEARBY_PRESENCE})
+    public @interface BroadcastType {
+    }
+
+    /**
+     * Tx Power when the value is not set in the broadcast.
+     */
+    public static final int UNKNOWN_TX_POWER = -100;
+
+    /**
+     * V0 of Nearby Presence Protocol.
+     */
+    public static final int PRESENCE_VERSION_V0 = 0;
+
+    /**
+     * V1 of Nearby Presence Protocol.
+     */
+    public static final int PRESENCE_VERSION_V1 = 1;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({PRESENCE_VERSION_V0, PRESENCE_VERSION_V1})
+    public @interface BroadcastVersion {
+    }
+
+    public static final @NonNull Creator<BroadcastRequest> CREATOR =
+            new Creator<BroadcastRequest>() {
+                @Override
+                public BroadcastRequest createFromParcel(Parcel in) {
+                    int type = in.readInt();
+                    switch (type) {
+                        case BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE:
+                            return PresenceBroadcastRequest.createFromParcelBody(in);
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected broadcast type (value " + type + ") in parcel.");
+                    }
+                }
+
+                @Override
+                public BroadcastRequest[] newArray(int size) {
+                    return new BroadcastRequest[size];
+                }
+            };
+
+    private final @BroadcastType int mType;
+    private final @BroadcastVersion int mVersion;
+    private final int mTxPower;
+    private final List<Integer> mMediums;
+
+    BroadcastRequest(@BroadcastType int type, @BroadcastVersion int version, int txPower,
+            List<Integer> mediums) {
+        this.mType = type;
+        this.mVersion = version;
+        this.mTxPower = txPower;
+        this.mMediums = mediums;
+    }
+
+    BroadcastRequest(@BroadcastType int type, Parcel in) {
+        mType = type;
+        mVersion = in.readInt();
+        mTxPower = in.readInt();
+        mMediums = new ArrayList<>();
+        in.readList(mMediums, Integer.class.getClassLoader(), Integer.class);
+    }
+
+    /**
+     * Returns the type of the broadcast.
+     */
+    public @BroadcastType int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the version of the broadcast.
+     */
+    public @BroadcastVersion int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Returns the calibrated TX power when this request is broadcast.
+     */
+    @IntRange(from = -127, to = 126)
+    public int getTxPower() {
+        return mTxPower;
+    }
+
+    /**
+     * Returns the list of broadcast mediums.
+     */
+    @NonNull
+    public List<Integer> getMediums() {
+        return mMediums;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mVersion);
+        dest.writeInt(mTxPower);
+        dest.writeList(mMediums);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/nearby/framework/java/android/nearby/CredentialElement.java b/nearby/framework/java/android/nearby/CredentialElement.java
new file mode 100644
index 0000000..d2049d1
--- /dev/null
+++ b/nearby/framework/java/android/nearby/CredentialElement.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an element in {@link PresenceCredential}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CredentialElement implements Parcelable {
+    private final String mKey;
+    private final byte[] mValue;
+
+    /**
+     * Constructs a {@link CredentialElement}.
+     */
+    public CredentialElement(@NonNull String key, @NonNull byte[] value) {
+        Preconditions.checkState(key != null && value != null,
+                "neither key or value can be null");
+        mKey = key;
+        mValue = value;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<CredentialElement> CREATOR =
+            new Parcelable.Creator<CredentialElement>() {
+                @Override
+                public CredentialElement createFromParcel(Parcel in) {
+                    String key = in.readString();
+                    byte[] value = new byte[in.readInt()];
+                    in.readByteArray(value);
+                    return new CredentialElement(key, value);
+                }
+
+                @Override
+                public CredentialElement[] newArray(int size) {
+                    return new CredentialElement[size];
+                }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mKey);
+        dest.writeInt(mValue.length);
+        dest.writeByteArray(mValue);
+    }
+
+    /**
+     * Returns the key of the credential element.
+     */
+    @NonNull
+    public String getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the value of the credential element.
+     */
+    @NonNull
+    public byte[] getValue() {
+        return mValue;
+    }
+}
diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java
new file mode 100644
index 0000000..6fa5fb5
--- /dev/null
+++ b/nearby/framework/java/android/nearby/DataElement.java
@@ -0,0 +1,89 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+
+/**
+ * Represents a data element in Nearby Presence.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DataElement implements Parcelable {
+
+    private final int mKey;
+    private final byte[] mValue;
+
+    /**
+     * Constructs a {@link DataElement}.
+     */
+    public DataElement(int key, @NonNull byte[] value) {
+        Preconditions.checkState(value != null, "value cannot be null");
+        mKey = key;
+        mValue = value;
+    }
+
+    @NonNull
+    public static final Creator<DataElement> CREATOR = new Creator<DataElement>() {
+        @Override
+        public DataElement createFromParcel(Parcel in) {
+            int key = in.readInt();
+            byte[] value = new byte[in.readInt()];
+            in.readByteArray(value);
+            return new DataElement(key, value);
+        }
+
+        @Override
+        public DataElement[] newArray(int size) {
+            return new DataElement[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mKey);
+        dest.writeInt(mValue.length);
+        dest.writeByteArray(mValue);
+    }
+
+    /**
+     * Returns the key of the data element, as defined in the nearby presence specification.
+     */
+    public int getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the value of the data element.
+     */
+    @NonNull
+    public byte[] getValue() {
+        return mValue;
+    }
+}
diff --git a/nearby/framework/java/android/nearby/FastPairDevice.aidl b/nearby/framework/java/android/nearby/FastPairDevice.aidl
new file mode 100644
index 0000000..5942966
--- /dev/null
+++ b/nearby/framework/java/android/nearby/FastPairDevice.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * A class represents a Fast Pair device that can be discovered by multiple mediums.
+ *
+ * {@hide}
+ */
+parcelable FastPairDevice;
diff --git a/nearby/framework/java/android/nearby/FastPairDevice.java b/nearby/framework/java/android/nearby/FastPairDevice.java
index 1e766a5..e12b4f8 100644
--- a/nearby/framework/java/android/nearby/FastPairDevice.java
+++ b/nearby/framework/java/android/nearby/FastPairDevice.java
@@ -22,7 +22,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -41,7 +43,10 @@
             if (in.readInt() == 1) {
                 builder.setName(in.readString());
             }
-            builder.setMedium(in.readInt());
+            int size = in.readInt();
+            for (int i = 0; i < size; i++) {
+                builder.addMedium(in.readInt());
+            }
             builder.setRssi(in.readInt());
             if (in.readInt() == 1) {
                 builder.setModelId(in.readString());
@@ -75,7 +80,7 @@
      * Creates a new FastPairDevice.
      *
      * @param name Name of the FastPairDevice. Can be {@code null} if there is no name.
-     * @param medium The {@link Medium} over which the device is discovered.
+     * @param mediums The {@link Medium}s over which the device is discovered.
      * @param rssi The received signal strength in dBm.
      * @param modelId The identifier of the Fast Pair device.
      *                Can be {@code null} if there is no Model ID.
@@ -83,44 +88,18 @@
      * @param data Extra data for a Fast Pair device.
      */
     public FastPairDevice(@Nullable String name,
-            @Medium int medium,
+            List<Integer> mediums,
             int rssi,
             @Nullable String modelId,
             @NonNull String bluetoothAddress,
             @Nullable byte[] data) {
-        super(name, medium, rssi);
+        super(name, mediums, rssi);
         this.mModelId = modelId;
         this.mBluetoothAddress = bluetoothAddress;
         this.mData = data;
     }
 
     /**
-     * Gets the name of the device, or {@code null} if not available.
-     *
-     * @hide
-     */
-    @Nullable
-    @Override
-    public String getName() {
-        return mName;
-    }
-
-    /** Gets the medium over which this device was discovered. */
-    @Override
-    public int getMedium() {
-        return mMedium;
-    }
-
-    /**
-     * Gets the received signal strength in dBm.
-     */
-    @IntRange(from = -127, to  = 126)
-    @Override
-    public int getRssi() {
-        return mRssi;
-    }
-
-    /**
      * Gets the identifier of the Fast Pair device. Can be {@code null} if there is no Model ID.
      */
     @Nullable
@@ -161,11 +140,15 @@
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append("FastPairDevice [");
-        if (mName != null && !mName.isEmpty()) {
-            stringBuilder.append("name=").append(mName).append(", ");
+        String name = getName();
+        if (getName() != null && !name.isEmpty()) {
+            stringBuilder.append("name=").append(name).append(", ");
         }
-        stringBuilder.append("medium=").append(mediumToString(mMedium));
-        stringBuilder.append(" rssi=").append(mRssi);
+        stringBuilder.append("medium={");
+        for (int medium: getMediums()) {
+            stringBuilder.append(mediumToString(medium));
+        }
+        stringBuilder.append("} rssi=").append(getRssi());
         stringBuilder.append(" modelId=").append(mModelId);
         stringBuilder.append(" bluetoothAddress=").append(mBluetoothAddress);
         stringBuilder.append("]");
@@ -189,17 +172,23 @@
     @Override
     public int hashCode() {
         return Objects.hash(
-                mName, mMedium, mRssi, mModelId, mBluetoothAddress, Arrays.hashCode(mData));
+                getName(), getMediums(), getRssi(), mModelId, mBluetoothAddress,
+                Arrays.hashCode(mData));
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mName == null ? 0 : 1);
-        if (mName != null) {
-            dest.writeString(mName);
+        String name = getName();
+        dest.writeInt(name == null ? 0 : 1);
+        if (name != null) {
+            dest.writeString(name);
         }
-        dest.writeInt(mMedium);
-        dest.writeInt(mRssi);
+        List<Integer> mediums = getMediums();
+        dest.writeInt(mediums.size());
+        for (int medium : mediums) {
+            dest.writeInt(medium);
+        }
+        dest.writeInt(getRssi());
         dest.writeInt(mModelId == null ? 0 : 1);
         if (mModelId != null) {
             dest.writeString(mModelId);
@@ -218,13 +207,18 @@
      * @hide
      */
     public static final class Builder {
+        private final List<Integer> mMediums;
 
         @Nullable private String mName;
-        @Medium private int mMedium;
         private int mRssi;
         @Nullable private String mModelId;
         private String mBluetoothAddress;
         @Nullable private byte[] mData;
+
+        public Builder() {
+            mMediums = new ArrayList<>();
+        }
+
         /**
          * Sets the name of the Fast Pair device.
          *
@@ -242,8 +236,8 @@
          * @param medium The {@link Medium} over which the device is discovered.
          */
         @NonNull
-        public Builder setMedium(@Medium int medium) {
-            mMedium = medium;
+        public Builder addMedium(@Medium int medium) {
+            mMediums.add(medium);
             return this;
         }
 
@@ -253,7 +247,7 @@
          * @param rssi The received signal strength in dBm.
          */
         @NonNull
-        public Builder setRssi(int rssi) {
+        public Builder setRssi(@IntRange(from = -127, to = 126) int rssi) {
             mRssi = rssi;
             return this;
         }
@@ -298,7 +292,7 @@
          */
         @NonNull
         public FastPairDevice build() {
-            return new FastPairDevice(mName, mMedium, mRssi, mModelId,
+            return new FastPairDevice(mName, mMediums, mRssi, mModelId,
                     mBluetoothAddress, mData);
         }
     }
diff --git a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
index 852791f..c051eb0 100644
--- a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
+++ b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
@@ -82,6 +82,14 @@
     }
 
     /**
+     * Get device name.
+     */
+    @Nullable
+    public String getName() {
+        return mMetadataParcel.name;
+    }
+
+    /**
      * Get true wireless image url for left bud.
      */
     @Nullable
@@ -311,6 +319,7 @@
             mBuilderParcel = new FastPairDeviceMetadataParcel();
             mBuilderParcel.imageUrl = null;
             mBuilderParcel.intentUri = null;
+            mBuilderParcel.name = null;
             mBuilderParcel.bleTxPower = 0;
             mBuilderParcel.triggerDistance = 0;
             mBuilderParcel.image = null;
@@ -369,6 +378,18 @@
         }
 
         /**
+         * Set device name.
+         *
+         * @param name Device name.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder setName(@Nullable String name) {
+            mBuilderParcel.name = name;
+            return this;
+        }
+
+        /**
          * Set ble transmission power.
          *
          * @param bleTxPower Ble transmission power.
diff --git a/nearby/framework/java/android/nearby/FastPairStatusCallback.java b/nearby/framework/java/android/nearby/FastPairStatusCallback.java
new file mode 100644
index 0000000..1567828
--- /dev/null
+++ b/nearby/framework/java/android/nearby/FastPairStatusCallback.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.annotation.NonNull;
+
+/**
+ * Reports the pair status for an ongoing pair with a {@link FastPairDevice}.
+ * @hide
+ */
+public interface FastPairStatusCallback {
+
+    /** Reports a pair status related metadata associated with a {@link FastPairDevice} */
+    void onPairUpdate(@NonNull FastPairDevice fastPairDevice,
+            PairStatusMetadata pairStatusMetadata);
+}
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
index 790b2ed..538940c 100644
--- a/nearby/framework/java/android/nearby/NearbyDevice.java
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -18,11 +18,13 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -34,27 +36,29 @@
 public abstract class NearbyDevice {
 
     @Nullable
-    final String mName;
+    private final String mName;
 
     @Medium
-    final int mMedium;
+    private final List<Integer> mMediums;
 
-    final int mRssi;
+    private final int mRssi;
 
     /**
      * Creates a new NearbyDevice.
      *
      * @param name Local device name. Can be {@code null} if there is no name.
-     * @param medium The {@link Medium} over which the device is discovered.
+     * @param mediums The {@link Medium}s over which the device is discovered.
      * @param rssi The received signal strength in dBm.
      * @hide
      */
-    public NearbyDevice(@Nullable String name, @Medium int medium, int rssi) {
-        Preconditions.checkState(isValidMedium(medium),
-                "Not supported medium: " + medium
-                        + ", scan medium must be one of NearbyDevice#Medium.");
+    public NearbyDevice(@Nullable String name, List<Integer> mediums, int rssi) {
+        for (int medium : mediums) {
+            Preconditions.checkState(isValidMedium(medium),
+                    "Not supported medium: " + medium
+                            + ", scan medium must be one of NearbyDevice#Medium.");
+        }
         mName = name;
-        mMedium = medium;
+        mMediums = mediums;
         mRssi = rssi;
     }
 
@@ -81,8 +85,6 @@
 
     /**
      * The name of the device, or null if not available.
-     *
-     * @hide
      */
     @Nullable
     public String getName() {
@@ -90,9 +92,9 @@
     }
 
     /** The medium over which this device was discovered. */
-    @Medium
-    public int getMedium() {
-        return mMedium;
+    @NonNull
+    @Medium public List<Integer> getMediums() {
+        return mMediums;
     }
 
     /**
@@ -110,8 +112,11 @@
         if (mName != null && !mName.isEmpty()) {
             stringBuilder.append("name=").append(mName).append(", ");
         }
-        stringBuilder.append("medium=").append(mediumToString(mMedium));
-        stringBuilder.append(" rssi=").append(mRssi);
+        stringBuilder.append("medium={");
+        for (int medium : mMediums) {
+            stringBuilder.append(mediumToString(medium));
+        }
+        stringBuilder.append("} rssi=").append(mRssi);
         stringBuilder.append("]");
         return stringBuilder.toString();
     }
@@ -121,7 +126,7 @@
         if (other instanceof NearbyDevice) {
             NearbyDevice otherDevice = (NearbyDevice) other;
             return Objects.equals(mName, otherDevice.mName)
-                    && mMedium == otherDevice.mMedium
+                    && mMediums == otherDevice.mMediums
                     && mRssi == otherDevice.mRssi;
         }
         return false;
@@ -129,7 +134,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mName, mMedium, mRssi);
+        return Objects.hash(mName, mMediums, mRssi);
     }
 
     /**
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 0f85519..3b0a776 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -60,6 +60,9 @@
                         builder.setFastPairModelId(in.readString());
                     }
                     if (in.readInt() == 1) {
+                        builder.setBluetoothAddress(in.readString());
+                    }
+                    if (in.readInt() == 1) {
                         int dataLength = in.readInt();
                         byte[] data = new byte[dataLength];
                         in.readByteArray(data);
@@ -125,7 +128,7 @@
             dest.writeString(mFastPairModelId);
         }
         dest.writeInt(mBluetoothAddress == null ? 0 : 1);
-        if (mFastPairModelId != null) {
+        if (mBluetoothAddress != null) {
             dest.writeString(mBluetoothAddress);
         }
         dest.writeInt(mData == null ? 0 : 1);
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 20f9164..7fb14ef 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -34,8 +34,8 @@
 import java.util.concurrent.Executor;
 
 /**
- * This class provides a way to perform Nearby related operations such as scanning and connecting
- * to nearby devices.
+ * This class provides a way to perform Nearby related operations such as scanning, broadcasting
+ * and connecting to nearby devices.
  *
  * <p> To get a {@link NearbyManager} instance, call the
  * <code>Context.getSystemService(NearbyManager.class)</code>.
@@ -67,7 +67,7 @@
         if (scanType == ScanRequest.SCAN_TYPE_FAST_PAIR) {
             return new FastPairDevice.Builder()
                     .setName(nearbyDeviceParcelable.getName())
-                    .setMedium(nearbyDeviceParcelable.getMedium())
+                    .addMedium(nearbyDeviceParcelable.getMedium())
                     .setRssi(nearbyDeviceParcelable.getRssi())
                     .setModelId(nearbyDeviceParcelable.getFastPairModelId())
                     .setBluetoothAddress(nearbyDeviceParcelable.getBluetoothAddress())
@@ -139,6 +139,29 @@
         }
     }
 
+
+    /**
+     * Start broadcasting the request using nearby specification.
+     *
+     * @param broadcastRequest Request for the nearby broadcast.
+     * @param executor Executor for running the callback.
+     * @param callback Callback for notifying the client..
+     */
+    public void startBroadcast(@NonNull BroadcastRequest broadcastRequest,
+            @CallbackExecutor @NonNull Executor executor, @NonNull BroadcastCallback callback) {
+        // TODO(b/218187205): implement broadcast.
+    }
+
+    /**
+     * Stop the broadcast associated with the given callback.
+     *
+     * @param callback The callback that was used for starting the broadcast.
+     */
+    @SuppressLint("ExecutorRegistration")
+    public void stopBroadcast(@NonNull BroadcastCallback callback) {
+        // TODO(b/218187205): implement broadcast.
+    }
+
     private static class ScanListenerTransport extends IScanListener.Stub {
 
         private @ScanRequest.ScanType int mScanType;
diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.aidl b/nearby/framework/java/android/nearby/PairStatusMetadata.aidl
new file mode 100644
index 0000000..911a300
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PairStatusMetadata.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Metadata about an ongoing paring. Wraps transient data like status and progress.
+ *
+ * @hide
+ */
+parcelable PairStatusMetadata;
diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.java b/nearby/framework/java/android/nearby/PairStatusMetadata.java
new file mode 100644
index 0000000..438cd6b
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PairStatusMetadata.java
@@ -0,0 +1,117 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Metadata about an ongoing paring. Wraps transient data like status and progress.
+ *
+ * @hide
+ */
+public final class PairStatusMetadata implements Parcelable {
+
+    @Status
+    private final int mStatus;
+
+    /** The status of the pairing. */
+    @IntDef({
+            Status.UNKNOWN,
+            Status.SUCCESS,
+            Status.FAIL,
+            Status.DISMISS
+    })
+    public @interface Status {
+        int UNKNOWN = 1000;
+        int SUCCESS = 1001;
+        int FAIL = 1002;
+        int DISMISS = 1003;
+    }
+
+    /** Converts the status to readable string. */
+    public static String statusToString(@Status int status) {
+        switch (status) {
+            case Status.SUCCESS:
+                return "SUCCESS";
+            case Status.FAIL:
+                return "FAIL";
+            case Status.DISMISS:
+                return "DISMISS";
+            case Status.UNKNOWN:
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    public int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "PairStatusMetadata[ status=" + statusToString(mStatus) + "]";
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof PairStatusMetadata) {
+            return mStatus == ((PairStatusMetadata) other).mStatus;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus);
+    }
+
+    public PairStatusMetadata(@Status int status) {
+        mStatus = status;
+    }
+
+    public static final Creator<PairStatusMetadata> CREATOR = new Creator<PairStatusMetadata>() {
+        @Override
+        public PairStatusMetadata createFromParcel(Parcel in) {
+            return new PairStatusMetadata(in.readInt());
+        }
+
+        @Override
+        public PairStatusMetadata[] newArray(int size) {
+            return new PairStatusMetadata[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int getStability() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mStatus);
+    }
+}
diff --git a/nearby/framework/java/android/nearby/PresenceBroadcastRequest.java b/nearby/framework/java/android/nearby/PresenceBroadcastRequest.java
new file mode 100644
index 0000000..b4d4b55
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PresenceBroadcastRequest.java
@@ -0,0 +1,216 @@
+/*
+ * 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 android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for Nearby Presence Broadcast.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PresenceBroadcastRequest extends BroadcastRequest implements Parcelable {
+    private final byte[] mSalt;
+    private final List<Integer> mActions;
+    private final PrivateCredential mCredential;
+    private final List<DataElement> mExtendedProperties;
+
+    private PresenceBroadcastRequest(@BroadcastVersion int version, int txPower,
+            List<Integer> mediums, byte[] salt, List<Integer> actions,
+            PrivateCredential credential, List<DataElement> extendedProperties) {
+        super(BROADCAST_TYPE_NEARBY_PRESENCE, version, txPower, mediums);
+        mSalt = salt;
+        mActions = actions;
+        mCredential = credential;
+        mExtendedProperties = extendedProperties;
+    }
+
+    private PresenceBroadcastRequest(Parcel in) {
+        super(BROADCAST_TYPE_NEARBY_PRESENCE, in);
+        mSalt = new byte[in.readInt()];
+        in.readByteArray(mSalt);
+
+        mActions = new ArrayList<>();
+        in.readList(mActions, Integer.class.getClassLoader(), Integer.class);
+        mCredential = in.readParcelable(PrivateCredential.class.getClassLoader(),
+                PrivateCredential.class);
+        mExtendedProperties = new ArrayList<>();
+        in.readList(mExtendedProperties, DataElement.class.getClassLoader(), DataElement.class);
+    }
+
+    @NonNull
+    public static final Creator<PresenceBroadcastRequest> CREATOR =
+            new Creator<PresenceBroadcastRequest>() {
+                @Override
+                public PresenceBroadcastRequest createFromParcel(Parcel in) {
+                    // Skip Broadcast request type - it's used by parent class.
+                    in.readInt();
+                    return createFromParcelBody(in);
+                }
+
+                @Override
+                public PresenceBroadcastRequest[] newArray(int size) {
+                    return new PresenceBroadcastRequest[size];
+                }
+            };
+
+    static PresenceBroadcastRequest createFromParcelBody(Parcel in) {
+        return new PresenceBroadcastRequest(in);
+    }
+
+    /**
+     * Returns the salt associated with this broadcast request.
+     */
+    @NonNull
+    public byte[] getSalt() {
+        return mSalt;
+    }
+
+    /**
+     * Returns actions associated with this broadcast request.
+     */
+    @NonNull
+    public List<Integer> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Returns the private credential associated with this broadcast request.
+     */
+    @NonNull
+    public PrivateCredential getCredential() {
+        return mCredential;
+    }
+
+    /**
+     * Returns extended property information associated with this broadcast request.
+     */
+    @NonNull
+    public List<DataElement> getExtendedProperties() {
+        return mExtendedProperties;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mSalt.length);
+        dest.writeByteArray(mSalt);
+        dest.writeList(mActions);
+        dest.writeParcelable(mCredential, /** parcelableFlags= */0);
+        dest.writeList(mExtendedProperties);
+    }
+
+    /**
+     * Builder for {@link PresenceBroadcastRequest}.
+     */
+    public static final class Builder {
+        private final List<Integer> mMediums;
+        private final List<Integer> mActions;
+        private final List<DataElement> mExtendedProperties;
+
+        private int mVersion;
+        private int mTxPower;
+        private byte[] mSalt;
+        private PrivateCredential mCredential;
+
+        public Builder(@NonNull List<Integer> mediums, @NonNull byte[] salt) {
+            Preconditions.checkState(!mediums.isEmpty(), "mediums cannot be empty");
+            Preconditions.checkState(salt != null && salt.length > 0, "salt cannot be empty");
+
+            mVersion = PRESENCE_VERSION_V0;
+            mTxPower = UNKNOWN_TX_POWER;
+            mActions = new ArrayList<>();
+            mExtendedProperties = new ArrayList<>();
+
+            mSalt = salt;
+            mMediums = mediums;
+        }
+
+        /**
+         * Sets the version for this request.
+         */
+        @NonNull
+        public Builder setVersion(@BroadcastVersion int version) {
+            mVersion = version;
+            return this;
+        }
+
+        /**
+         * Sets the calibrated tx power level in dBm for this request. The tx power level should
+         * be between -127 dBm and 126 dBm.
+         */
+        @NonNull
+        public Builder setTxPower(@IntRange(from = -127, to = 126) int txPower) {
+            mTxPower = txPower;
+            return this;
+        }
+
+        /**
+         * Adds an action for the presence broadcast request.
+         */
+        @NonNull
+        public Builder addAction(@IntRange(from = 1, to = 255) int action) {
+            mActions.add(action);
+            return this;
+        }
+
+        /**
+         * Sets the credential associated with the presence broadcast request.
+         */
+        @NonNull
+        public Builder setCredential(@NonNull PrivateCredential credential) {
+            Objects.requireNonNull(credential);
+            mCredential = credential;
+            return this;
+        }
+
+        /**
+         * Adds an extended property for the presence broadcast request.
+         */
+        @NonNull
+        public Builder addExtendedProperty(@NonNull DataElement dataElement) {
+            Objects.requireNonNull(dataElement);
+            mExtendedProperties.add(dataElement);
+            return this;
+        }
+
+        /**
+         * Builds a {@link PresenceBroadcastRequest}.
+         */
+        @NonNull
+        public PresenceBroadcastRequest build() {
+            return new PresenceBroadcastRequest(mVersion, mTxPower, mMediums, mSalt, mActions,
+                    mCredential, mExtendedProperties);
+        }
+    }
+}
diff --git a/nearby/framework/java/android/nearby/PresenceCredential.java b/nearby/framework/java/android/nearby/PresenceCredential.java
new file mode 100644
index 0000000..fae3603
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PresenceCredential.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a credential for Nearby Presence.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("ParcelNotFinal")  // PresenceCredential constructor is not public
+public abstract class PresenceCredential implements Parcelable {
+    /**
+     * Private credential type.
+     */
+    public static final int CREDENTIAL_TYPE_PRIVATE = 0;
+
+    /**
+     * Public credential type.
+     */
+    public static final int CREDENTIAL_TYPE_PUBLIC = 1;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CREDENTIAL_TYPE_PUBLIC, CREDENTIAL_TYPE_PRIVATE})
+    public @interface CredentialType {
+    }
+
+    /**
+     * Unknown identity type.
+     */
+    public static final int IDENTITY_TYPE_UNKNOWN = 0;
+
+    /**
+     * Private identity type.
+     */
+    public static final int IDENTITY_TYPE_PRIVATE = 1;
+    /**
+     * Provisioned identity type.
+     */
+    public static final int IDENTITY_TYPE_PROVISIONED = 2;
+    /**
+     * Trusted identity type.
+     */
+    public static final int IDENTITY_TYPE_TRUSTED = 3;
+    /**
+     * Public identity type.
+     */
+    public static final int IDENTITY_TYPE_PUBLIC = 4;
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({IDENTITY_TYPE_UNKNOWN, IDENTITY_TYPE_PRIVATE, IDENTITY_TYPE_PROVISIONED,
+            IDENTITY_TYPE_TRUSTED, IDENTITY_TYPE_PUBLIC})
+    public @interface IdentityType {
+    }
+
+    private final @CredentialType int mType;
+    private final @IdentityType int mIdentityType;
+    private final byte[] mSecretId;
+    private final byte[] mAuthenticityKey;
+    private final List<CredentialElement> mCredentialElements;
+
+    PresenceCredential(@CredentialType int type, @IdentityType int identityType,
+            byte[] secretId, byte[] authenticityKey, List<CredentialElement> credentialElements) {
+        mType = type;
+        mIdentityType = identityType;
+        mSecretId = secretId;
+        mAuthenticityKey = authenticityKey;
+        mCredentialElements = credentialElements;
+    }
+
+    PresenceCredential(@CredentialType int type, Parcel in) {
+        mType = type;
+        mIdentityType = in.readInt();
+        mSecretId = new byte[in.readInt()];
+        in.readByteArray(mSecretId);
+        mAuthenticityKey = new byte[in.readInt()];
+        in.readByteArray(mAuthenticityKey);
+        mCredentialElements = new ArrayList<>();
+        in.readList(mCredentialElements, CredentialElement.class.getClassLoader(),
+                CredentialElement.class);
+    }
+
+
+    @NonNull
+    public static final Creator<PresenceCredential> CREATOR = new Creator<PresenceCredential>() {
+        @Override
+        public PresenceCredential createFromParcel(Parcel in) {
+            int type = in.readInt();
+            switch (type) {
+                case CREDENTIAL_TYPE_PRIVATE:
+                    return PrivateCredential.createFromParcelBody(in);
+                case CREDENTIAL_TYPE_PUBLIC:
+                    return PublicCredential.createFromParcelBody(in);
+                default:
+                    throw new IllegalStateException(
+                            "Unexpected credential type (value " + type + ") in parcel.");
+            }
+        }
+
+        @Override
+        public PresenceCredential[] newArray(int size) {
+            return new PresenceCredential[size];
+        }
+    };
+
+    /**
+     * Returns the type of the credential.
+     */
+    public @CredentialType int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the identity type of the credential.
+     */
+    public @IdentityType int getIdentityType() {
+        return mIdentityType;
+    }
+
+    /**
+     * Returns the secret id of the credential.
+     */
+    @NonNull
+    public byte[] getSecretId() {
+        return mSecretId;
+    }
+
+    /**
+     * Returns the authenticity key of the credential.
+     */
+    @NonNull
+    public byte[] getAuthenticityKey() {
+        return mAuthenticityKey;
+    }
+
+    /**
+     * Returns the elements of the credential.
+     */
+    @NonNull
+    public List<CredentialElement> getCredentialElements() {
+        return mCredentialElements;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mIdentityType);
+        dest.writeInt(mSecretId.length);
+        dest.writeByteArray(mSecretId);
+        dest.writeInt(mAuthenticityKey.length);
+        dest.writeByteArray(mAuthenticityKey);
+        dest.writeList(mCredentialElements);
+    }
+}
diff --git a/nearby/framework/java/android/nearby/PresenceDevice.java b/nearby/framework/java/android/nearby/PresenceDevice.java
index 61326c6..d5ea0b4 100644
--- a/nearby/framework/java/android/nearby/PresenceDevice.java
+++ b/nearby/framework/java/android/nearby/PresenceDevice.java
@@ -19,18 +19,22 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Bundle;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 /**
  * Represents a Presence device from nearby scans.
  *
  * @hide
  */
+@SystemApi
 public final class PresenceDevice extends NearbyDevice implements Parcelable {
 
     /** The type of presence device. */
@@ -63,21 +67,13 @@
     }
 
     private final String mDeviceId;
+    private final byte[] mSalt;
+    private final byte[] mSecretId;
+    private final byte[] mEncryptedIdentity;
     private final int mDeviceType;
     private final String mDeviceImageUrl;
     private final long mDiscoveryTimestampMillis;
-    private final Bundle mExtendedProperties;
-
-    /**
-     * Gets the name of the device, or {@code null} if not available.
-     *
-     * @hide
-     */
-    @Nullable
-    @Override
-    public String getName() {
-        return mName;
-    }
+    private final List<DataElement> mExtendedProperties;
 
     /**
      * The id of the device.
@@ -89,6 +85,30 @@
         return mDeviceId;
     }
 
+    /**
+     * Returns the salt used when presence device is discovered.
+     */
+    @NonNull
+    public byte[] getSalt() {
+        return mSalt;
+    }
+
+    /**
+     * Returns the secret used when presence device is discovered.
+     */
+    @NonNull
+    public byte[] getSecretId() {
+        return mSecretId;
+    }
+
+    /**
+     * Returns the encrypted identity used when presence device is discovered.
+     */
+    @NonNull
+    public byte[] getEncryptedIdentity() {
+        return mEncryptedIdentity;
+    }
+
     /** The type of the device. */
     @DeviceType
     public int getDeviceType() {
@@ -110,17 +130,19 @@
      * The extended properties of the device.
      */
     @NonNull
-    public Bundle getExtendedProperties() {
+    public List<DataElement> getExtendedProperties() {
         return mExtendedProperties;
     }
 
-    private PresenceDevice(String deviceName, int mMedium, int rssi, String deviceId,
-            int deviceType,
+    private PresenceDevice(String deviceName, List<Integer> mMediums, int rssi, String deviceId,
+            byte[] salt, byte[] secretId, byte[] encryptedIdentity, int deviceType,
             String deviceImageUrl, long discoveryTimestampMillis,
-            Bundle extendedProperties) {
-        // TODO (b/217462253): change medium to a set in NearbyDevice.
-        super(deviceName, mMedium, rssi);
+            List<DataElement> extendedProperties) {
+        super(deviceName, mMediums, rssi);
         mDeviceId = deviceId;
+        mSalt = salt;
+        mSecretId = secretId;
+        mEncryptedIdentity = encryptedIdentity;
         mDeviceType = deviceType;
         mDeviceImageUrl = deviceImageUrl;
         mDiscoveryTimestampMillis = discoveryTimestampMillis;
@@ -129,12 +151,17 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mName == null ? 0 : 1);
-        if (mName != null) {
-            dest.writeString(mName);
+        String name = getName();
+        dest.writeInt(name == null ? 0 : 1);
+        if (name != null) {
+            dest.writeString(name);
         }
-        dest.writeInt(mMedium);
-        dest.writeInt(mRssi);
+        List<Integer> mediums = getMediums();
+        dest.writeInt(mediums.size());
+        for (int medium : mediums) {
+            dest.writeInt(medium);
+        }
+        dest.writeInt(getRssi());
         dest.writeString(mDeviceId);
         dest.writeInt(mDeviceType);
         dest.writeInt(mDeviceImageUrl == null ? 0 : 1);
@@ -142,7 +169,10 @@
             dest.writeString(mDeviceImageUrl);
         }
         dest.writeLong(mDiscoveryTimestampMillis);
-        dest.writeBundle(mExtendedProperties);
+        dest.writeInt(mExtendedProperties.size());
+        for (DataElement dataElement : mExtendedProperties) {
+            dest.writeParcelable(dataElement, 0);
+        }
     }
 
     @Override
@@ -158,7 +188,10 @@
             if (in.readInt() == 1) {
                 builder.setName(in.readString());
             }
-            builder.setMedium(in.readInt());
+            int size = in.readInt();
+            for (int i = 0; i < size; i++) {
+                builder.addMedium(in.readInt());
+            }
             builder.setRssi(in.readInt());
             builder.setDeviceId(in.readString());
             builder.setDeviceType(in.readInt());
@@ -166,9 +199,10 @@
                 builder.setDeviceImageUrl(in.readString());
             }
             builder.setDiscoveryTimestampMillis(in.readLong());
-            Bundle bundle = in.readBundle();
-            for (String key : bundle.keySet()) {
-                builder.addExtendedProperty(key, bundle.getCharSequence(key).toString());
+            int dataElementSize = in.readInt();
+            for (int i = 0; i < dataElementSize; i++) {
+                builder.addExtendedProperty(
+                        in.readParcelable(DataElement.class.getClassLoader(), DataElement.class));
             }
             return builder.build();
         }
@@ -181,23 +215,25 @@
 
     /**
      * Builder class for {@link PresenceDevice}.
-     *
-     * @hide
      */
     public static final class Builder {
 
-        private final Bundle mExtendedProperties;
+        private final List<DataElement> mExtendedProperties;
+        private final List<Integer> mMediums;
 
         private String mName;
         private int mRssi;
-        private int mMedium;
         private String mDeviceId;
+        private byte[] mSalt;
+        private byte[] mSecretId;
+        private byte[] mEncryptedIdentity;
         private int mDeviceType;
         private String mDeviceImageUrl;
         private long mDiscoveryTimestampMillis;
 
         public Builder() {
-            mExtendedProperties = new Bundle();
+            mMediums = new ArrayList<>();
+            mExtendedProperties = new ArrayList<>();
             mRssi = -100;
         }
 
@@ -207,19 +243,19 @@
          * @param name Name of the Presence. Can be {@code null} if there is no name.
          */
         @NonNull
-        public Builder setName(@android.annotation.Nullable String name) {
+        public Builder setName(@Nullable String name) {
             mName = name;
             return this;
         }
 
         /**
-         * Sets the medium over which the Presence device is discovered.
+         * Adds the medium over which the Presence device is discovered.
          *
          * @param medium The {@link Medium} over which the device is discovered.
          */
         @NonNull
-        public Builder setMedium(@Medium int medium) {
-            mMedium = medium;
+        public Builder addMedium(@Medium int medium) {
+            mMediums.add(medium);
             return this;
         }
 
@@ -241,10 +277,40 @@
          */
         @NonNull
         public Builder setDeviceId(@NonNull String deviceId) {
+            Objects.requireNonNull(deviceId);
             mDeviceId = deviceId;
             return this;
         }
 
+        /**
+         * Sets the identifier on the discovered Presence device.
+         */
+        @NonNull
+        public Builder setSalt(@NonNull byte[] salt) {
+            Objects.requireNonNull(salt);
+            mSalt = salt;
+            return this;
+        }
+
+        /**
+         * Sets the secret id of the discovered Presence device.
+         */
+        @NonNull
+        public Builder setSecretId(@NonNull byte[] secretId) {
+            Objects.requireNonNull(secretId);
+            mSecretId = secretId;
+            return this;
+        }
+
+        /**
+         * Sets the encrypted identity of the discovered Presence device.
+         */
+        @NonNull
+        public Builder setEncryptedIdentity(@NonNull byte[] encryptedIdentity) {
+            Objects.requireNonNull(encryptedIdentity);
+            mEncryptedIdentity = encryptedIdentity;
+            return this;
+        }
 
         /**
          * Sets the type of discovered Presence device.
@@ -252,7 +318,7 @@
          * @param deviceType Type of the Presence device.
          */
         @NonNull
-        public Builder setDeviceType(int deviceType) {
+        public Builder setDeviceType(@DeviceType int deviceType) {
             mDeviceType = deviceType;
             return this;
         }
@@ -264,7 +330,7 @@
          * @param deviceImageUrl Url of the image for the Presence device.
          */
         @NonNull
-        public Builder setDeviceImageUrl(@NonNull String deviceImageUrl) {
+        public Builder setDeviceImageUrl(@Nullable String deviceImageUrl) {
             mDeviceImageUrl = deviceImageUrl;
             return this;
         }
@@ -285,12 +351,12 @@
         /**
          * Adds an extended property of the discovered presence device.
          *
-         * @param key   Key of the extended property.
-         * @param value Value of the extended property,
+         * @param dataElement Data element of the extended property.
          */
         @NonNull
-        public Builder addExtendedProperty(@NonNull String key, @NonNull String value) {
-            mExtendedProperties.putCharSequence(key, value);
+        public Builder addExtendedProperty(@NonNull DataElement dataElement) {
+            Objects.requireNonNull(dataElement);
+            mExtendedProperties.add(dataElement);
             return this;
         }
 
@@ -299,7 +365,9 @@
          */
         @NonNull
         public PresenceDevice build() {
-            return new PresenceDevice(mName, mMedium, mRssi, mDeviceId, mDeviceType,
+            return new PresenceDevice(mName, mMediums, mRssi, mDeviceId,
+                    mSalt, mSecretId, mEncryptedIdentity,
+                    mDeviceType,
                     mDeviceImageUrl,
                     mDiscoveryTimestampMillis, mExtendedProperties);
         }
diff --git a/nearby/framework/java/android/nearby/PresenceScanFilter.java b/nearby/framework/java/android/nearby/PresenceScanFilter.java
index 61e5049..f0c3c06 100644
--- a/nearby/framework/java/android/nearby/PresenceScanFilter.java
+++ b/nearby/framework/java/android/nearby/PresenceScanFilter.java
@@ -16,9 +16,9 @@
 
 package android.nearby;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -34,27 +35,19 @@
  *
  * @hide
  */
+@SystemApi
 public final class PresenceScanFilter extends ScanFilter implements Parcelable {
 
-    private final List<byte[]> mCertificates;
-    private final List<Integer> mPresenceIdentities;
+    private final List<PublicCredential> mCredentials;
     private final List<Integer> mPresenceActions;
-    private final Bundle mExtendedProperties;
+    private final List<DataElement> mExtendedProperties;
 
     /**
-     * A list of certificates to filter on.
+     * A list of credentials to filter on.
      */
     @NonNull
-    public List<byte[]> getCertificates() {
-        return mCertificates;
-    }
-
-    /**
-     * A list of presence identities for matching.
-     */
-    @NonNull
-    public List<Integer> getPresenceIdentities() {
-        return mPresenceIdentities;
+    public List<PublicCredential> getCredentials() {
+        return mCredentials;
     }
 
     /**
@@ -69,42 +62,33 @@
      * A bundle of extended properties for matching.
      */
     @NonNull
-    public Bundle getExtendedProperties() {
+    public List<DataElement> getExtendedProperties() {
         return mExtendedProperties;
     }
 
-    private PresenceScanFilter(int rssiThreshold, List<byte[]> certificates,
-            List<Integer> presenceIdentities, List<Integer> presenceActions,
-            Bundle extendedProperties) {
+    private PresenceScanFilter(int rssiThreshold, List<PublicCredential> credentials,
+            List<Integer> presenceActions, List<DataElement> extendedProperties) {
         super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, rssiThreshold);
-        mCertificates = new ArrayList<>(certificates);
-        mPresenceIdentities = new ArrayList<>(presenceIdentities);
+        mCredentials = new ArrayList<>(credentials);
         mPresenceActions = new ArrayList<>(presenceActions);
         mExtendedProperties = extendedProperties;
     }
 
     private PresenceScanFilter(Parcel in) {
         super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, in);
-        mCertificates = new ArrayList<>();
-        int size = in.readInt();
-        for (int i = 0; i < size; i++) {
-            int len = in.readInt();
-            byte[] certificate = new byte[len];
-            in.readByteArray(certificate);
-            mCertificates.add(certificate);
-        }
-        mPresenceIdentities = new ArrayList<>();
+        mCredentials = new ArrayList<>();
         if (in.readInt() != 0) {
-            in.readList(mPresenceIdentities, Integer.class.getClassLoader(), Integer.class);
+            in.readParcelableList(mCredentials, PublicCredential.class.getClassLoader(),
+                    PublicCredential.class);
         }
         mPresenceActions = new ArrayList<>();
         if (in.readInt() != 0) {
             in.readList(mPresenceActions, Integer.class.getClassLoader(), Integer.class);
         }
-        mExtendedProperties = new Bundle();
-        Bundle bundle = in.readBundle(getClass().getClassLoader());
-        for (String key : bundle.keySet()) {
-            mExtendedProperties.putString(key, bundle.getString(key));
+        mExtendedProperties = new ArrayList<>();
+        if (in.readInt() != 0) {
+            in.readParcelableList(mExtendedProperties, DataElement.class.getClassLoader(),
+                    DataElement.class);
         }
     }
 
@@ -138,75 +122,66 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeInt(mCertificates.size());
-        for (byte[] certificate : mCertificates) {
-            dest.writeInt(certificate.length);
-            dest.writeByteArray(certificate);
-        }
-        dest.writeInt(mPresenceIdentities.size());
-        if (!mPresenceIdentities.isEmpty()) {
-            dest.writeList(mPresenceIdentities);
+        dest.writeInt(mCredentials.size());
+        if (!mCredentials.isEmpty()) {
+            dest.writeParcelableList(mCredentials, 0);
         }
         dest.writeInt(mPresenceActions.size());
         if (!mPresenceActions.isEmpty()) {
             dest.writeList(mPresenceActions);
         }
-        dest.writeBundle(mExtendedProperties);
+        dest.writeInt(mExtendedProperties.size());
+        if (!mExtendedProperties.isEmpty()) {
+            dest.writeList(mExtendedProperties);
+        }
     }
 
     /**
      * Builder for {@link PresenceScanFilter}.
-     *
-     * @hide
      */
     public static final class Builder {
-        private int mRssiThreshold;
-        private final Set<byte[]> mCertificates;
+        private int mMaxPathLoss;
+        private final Set<PublicCredential> mCredentials;
         private final Set<Integer> mPresenceIdentities;
         private final Set<Integer> mPresenceActions;
-        private final Bundle mExtendedProperties;
+        private final List<DataElement> mExtendedProperties;
 
         public Builder() {
-            mRssiThreshold = -100;
-            mCertificates = new ArraySet<>();
+            mMaxPathLoss = 127;
+            mCredentials = new ArraySet<>();
             mPresenceIdentities = new ArraySet<>();
             mPresenceActions = new ArraySet<>();
-            mExtendedProperties = new Bundle();
+            mExtendedProperties = new ArrayList<>();
         }
 
         /**
-         * Sets the rssi threshold for the scan request.
+         * Sets the max path loss (in dBm) for the scan request. The path loss is the attenuation
+         * of radio energy between sender and receiver. Path loss here is defined as (TxPower -
+         * Rssi).
          */
         @NonNull
-        public Builder setRssiThreshold(int rssiThreshold) {
-            mRssiThreshold = rssiThreshold;
+        public Builder setMaxPathLoss(@IntRange(from = 0, to = 127) int maxPathLoss) {
+            mMaxPathLoss = maxPathLoss;
             return this;
         }
 
         /**
-         * Adds a list of certificates the scan filter is expected to match.
+         * Adds a credential the scan filter is expected to match.
          */
 
         @NonNull
-        public Builder addCertificate(@NonNull byte[] certificate) {
-            mCertificates.add(certificate);
+        public Builder addCredential(@NonNull PublicCredential credential) {
+            Objects.requireNonNull(credential);
+            mCredentials.add(credential);
             return this;
         }
 
         /**
-         * Adds a presence identity for filtering.
+         * Adds a presence action for filtering, which is an action the discoverer could take
+         * when it receives the broadcast of a presence device.
          */
         @NonNull
-        public Builder addPresenceIdentity(int identity) {
-            mPresenceIdentities.add(identity);
-            return this;
-        }
-
-        /**
-         * Adds a presence action for filtering.
-         */
-        @NonNull
-        public Builder addPresenceAction(int action) {
+        public Builder addPresenceAction(@IntRange(from = 1, to = 255) int action) {
             mPresenceActions.add(action);
             return this;
         }
@@ -215,8 +190,9 @@
          * Add an extended property for scan filtering.
          */
         @NonNull
-        public Builder addExtendedProperty(@NonNull String key, @Nullable String value) {
-            mExtendedProperties.putCharSequence(key, value);
+        public Builder addExtendedProperty(@NonNull DataElement dataElement) {
+            Objects.requireNonNull(dataElement);
+            mExtendedProperties.add(dataElement);
             return this;
         }
 
@@ -225,9 +201,9 @@
          */
         @NonNull
         public PresenceScanFilter build() {
-            Preconditions.checkState(!mCertificates.isEmpty(), "certificates cannot be empty");
-            return new PresenceScanFilter(mRssiThreshold, new ArrayList<>(mCertificates),
-                    new ArrayList<>(mPresenceIdentities),
+            Preconditions.checkState(!mCredentials.isEmpty(), "credentials cannot be empty");
+            return new PresenceScanFilter(mMaxPathLoss,
+                    new ArrayList<>(mCredentials),
                     new ArrayList<>(mPresenceActions),
                     mExtendedProperties);
         }
diff --git a/nearby/framework/java/android/nearby/PrivateCredential.java b/nearby/framework/java/android/nearby/PrivateCredential.java
new file mode 100644
index 0000000..8db49fa
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PrivateCredential.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a private credential.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PrivateCredential extends PresenceCredential implements Parcelable {
+
+    @NonNull
+    public static final Creator<PrivateCredential> CREATOR = new Creator<PrivateCredential>() {
+        @Override
+        public PrivateCredential createFromParcel(Parcel in) {
+            in.readInt(); // Skip the type as it's used by parent class only.
+            return createFromParcelBody(in);
+        }
+
+        @Override
+        public PrivateCredential[] newArray(int size) {
+            return new PrivateCredential[size];
+        }
+    };
+
+    private byte[] mMetadataEncryptionKey;
+    private String mDeviceName;
+
+    private PrivateCredential(Parcel in) {
+        super(CREDENTIAL_TYPE_PRIVATE, in);
+        mMetadataEncryptionKey = new byte[in.readInt()];
+        in.readByteArray(mMetadataEncryptionKey);
+        mDeviceName = in.readString();
+    }
+
+    private PrivateCredential(int identityType, byte[] secretId,
+            String deviceName, byte[] authenticityKey, List<CredentialElement> credentialElements,
+            byte[] metadataEncryptionKey) {
+        super(CREDENTIAL_TYPE_PRIVATE, identityType, secretId, authenticityKey,
+                credentialElements);
+        mDeviceName = deviceName;
+        mMetadataEncryptionKey = metadataEncryptionKey;
+    }
+
+    static PrivateCredential createFromParcelBody(Parcel in) {
+        return new PrivateCredential(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mMetadataEncryptionKey.length);
+        dest.writeByteArray(mMetadataEncryptionKey);
+        dest.writeString(mDeviceName);
+    }
+
+    /**
+     * Returns the metadata encryption key associated with this credential.
+     */
+    @NonNull
+    public byte[] getMetadataEncryptionKey() {
+        return mMetadataEncryptionKey;
+    }
+
+    /**
+     * Returns the device name associated with this credential.
+     */
+    @NonNull
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    /**
+     * Builder class for {@link PresenceCredential}.
+     */
+    public static final class Builder {
+        private final List<CredentialElement> mCredentialElements;
+
+        private @IdentityType int mIdentityType;
+        private byte[] mSecretId;
+        private byte[] mAuthenticityKey;
+        private byte[] mMetadataEncryptionKey;
+        private String mDeviceName;
+
+        public Builder(@NonNull byte[] secretId, @NonNull byte[] authenticityKey) {
+            Preconditions.checkState(secretId != null && secretId.length > 0,
+                    "secret id cannot be empty");
+            Preconditions.checkState(authenticityKey != null && authenticityKey.length > 0,
+                    "authenticity key cannot be empty");
+            mSecretId = secretId;
+            mAuthenticityKey = authenticityKey;
+            mCredentialElements = new ArrayList<>();
+        }
+
+        /**
+         * Sets the identity type for the presence credential.
+         */
+        @NonNull
+        public Builder setIdentityType(@IdentityType int identityType) {
+            mIdentityType = identityType;
+            return this;
+        }
+
+        /**
+         * Sets the metadata encryption key to the credential.
+         */
+        @NonNull
+        public Builder setMetadataEncryptionKey(@NonNull byte[] metadataEncryptionKey) {
+            mMetadataEncryptionKey = metadataEncryptionKey;
+            return this;
+        }
+
+        /**
+         * Sets the device name of the credential.
+         */
+        @NonNull
+        public Builder setDeviceName(@NonNull String deviceName) {
+            mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * Adds an element to the credential.
+         */
+        @NonNull
+        public Builder addCredentialElement(@NonNull CredentialElement credentialElement) {
+            mCredentialElements.add(credentialElement);
+            return this;
+        }
+
+        /**
+         * Builds the {@link PresenceCredential}.
+         */
+        @NonNull
+        public PrivateCredential build() {
+            return new PrivateCredential(mIdentityType, mSecretId, mDeviceName,
+                    mAuthenticityKey, mCredentialElements, mMetadataEncryptionKey);
+        }
+
+    }
+}
diff --git a/nearby/framework/java/android/nearby/PublicCredential.java b/nearby/framework/java/android/nearby/PublicCredential.java
new file mode 100644
index 0000000..715e7fd
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PublicCredential.java
@@ -0,0 +1,196 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a public credential.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PublicCredential extends PresenceCredential implements Parcelable {
+    @NonNull
+    public static final Creator<PublicCredential> CREATOR = new Creator<PublicCredential>() {
+        @Override
+        public PublicCredential createFromParcel(Parcel in) {
+            in.readInt(); // Skip the type as it's used by parent class only.
+            return createFromParcelBody(in);
+        }
+
+        @Override
+        public PublicCredential[] newArray(int size) {
+            return new PublicCredential[size];
+        }
+    };
+
+    private final byte[] mPublicKey;
+    private final byte[] mEncryptedMetadata;
+    private final byte[] mEncryptedMetadataKeyTag;
+
+    private PublicCredential(int identityType, byte[] secretId, byte[] authenticityKey,
+            List<CredentialElement> credentialElements, byte[] publicKey, byte[] encryptedMetadata,
+            byte[] metadataEncryptionKeyTag) {
+        super(CREDENTIAL_TYPE_PUBLIC, identityType, secretId, authenticityKey, credentialElements);
+        mPublicKey = publicKey;
+        mEncryptedMetadata = encryptedMetadata;
+        mEncryptedMetadataKeyTag = metadataEncryptionKeyTag;
+    }
+
+    private PublicCredential(Parcel in) {
+        super(CREDENTIAL_TYPE_PUBLIC, in);
+        mPublicKey = new byte[in.readInt()];
+        in.readByteArray(mPublicKey);
+        mEncryptedMetadata = new byte[in.readInt()];
+        in.readByteArray(mEncryptedMetadata);
+        mEncryptedMetadataKeyTag = new byte[in.readInt()];
+        in.readByteArray(mEncryptedMetadataKeyTag);
+    }
+
+    static PublicCredential createFromParcelBody(Parcel in) {
+        return new PublicCredential(in);
+    }
+
+    /**
+     * Returns the public key associated with this credential.
+     */
+    @NonNull
+    public byte[] getPublicKey() {
+        return mPublicKey;
+    }
+
+    /**
+     * Returns the encrypted metadata associated with this credential.
+     */
+    @NonNull
+    public byte[] getEncryptedMetadata() {
+        return mEncryptedMetadata;
+    }
+
+    /**
+     * Returns the metadata encryption key tag associated with this credential.
+     */
+    @NonNull
+    public byte[] getEncryptedMetadataKeyTag() {
+        return mEncryptedMetadataKeyTag;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mPublicKey.length);
+        dest.writeByteArray(mPublicKey);
+        dest.writeInt(mEncryptedMetadata.length);
+        dest.writeByteArray(mEncryptedMetadata);
+        dest.writeInt(mEncryptedMetadataKeyTag.length);
+        dest.writeByteArray(mEncryptedMetadataKeyTag);
+    }
+
+    /**
+     * Builder class for {@link PresenceCredential}.
+     */
+    public static final class Builder {
+        private final List<CredentialElement> mCredentialElements;
+
+        private @IdentityType int mIdentityType;
+        private byte[] mSecretId;
+        private byte[] mAuthenticityKey;
+        private byte[] mPublicKey;
+        private byte[] mEncryptedMetadata;
+        private byte[] mEncryptedMetadataKeyTag;
+
+        public Builder(@NonNull byte[] secretId, @NonNull byte[] authenticityKey) {
+            Objects.requireNonNull(secretId);
+            Objects.requireNonNull(authenticityKey);
+            mSecretId = secretId;
+            mAuthenticityKey = authenticityKey;
+            mCredentialElements = new ArrayList<>();
+        }
+
+        /**
+         * Sets the identity type for the presence credential.
+         */
+        @NonNull
+        public Builder setIdentityType(@IdentityType int identityType) {
+            mIdentityType = identityType;
+            return this;
+        }
+
+        /**
+         * Adds an element to the credential.
+         */
+        @NonNull
+        public Builder addCredentialElement(@NonNull CredentialElement credentialElement) {
+            Objects.requireNonNull(credentialElement);
+            mCredentialElements.add(credentialElement);
+            return this;
+        }
+
+        /**
+         * Sets the public key for the credential.
+         */
+        @NonNull
+        public Builder setPublicKey(@NonNull byte[] publicKey) {
+            Objects.requireNonNull(publicKey);
+            mPublicKey = publicKey;
+            return this;
+        }
+
+        /**
+         * Sets the encrypted metadata.
+         */
+        @NonNull
+        public Builder setEncryptedMetadata(@NonNull byte[] encryptedMetadata) {
+            Objects.requireNonNull(encryptedMetadata);
+            mEncryptedMetadata = encryptedMetadata;
+            return this;
+        }
+
+        /**
+         * Sets the encrypted metadata key tag.
+         */
+        @NonNull
+        public Builder setEncryptedMetadataKeyTag(@NonNull byte[] encryptedMetadataKeyTag) {
+            Objects.requireNonNull(encryptedMetadataKeyTag);
+            mEncryptedMetadataKeyTag = encryptedMetadataKeyTag;
+            return this;
+        }
+
+        /**
+         * Builds the {@link PresenceCredential}.
+         */
+        @NonNull
+        public PublicCredential build() {
+            return new PublicCredential(mIdentityType, mSecretId, mAuthenticityKey,
+                    mCredentialElements, mPublicKey, mEncryptedMetadata, mEncryptedMetadataKeyTag);
+        }
+
+    }
+}
diff --git a/nearby/framework/java/android/nearby/ScanFilter.java b/nearby/framework/java/android/nearby/ScanFilter.java
index 0b2a754..e16c6a0 100644
--- a/nearby/framework/java/android/nearby/ScanFilter.java
+++ b/nearby/framework/java/android/nearby/ScanFilter.java
@@ -16,8 +16,10 @@
 
 package android.nearby;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -26,6 +28,7 @@
  *
  * @hide
  */
+@SystemApi
 @SuppressLint("ParcelNotFinal")  // ScanFilter constructor is not public
 public abstract class ScanFilter implements Parcelable {
     public static final @NonNull Creator<ScanFilter> CREATOR = new Creator<ScanFilter>() {
@@ -33,6 +36,8 @@
         public ScanFilter createFromParcel(Parcel in) {
             int type = in.readInt();
             switch (type) {
+                // Currently, only Nearby Presence filtering is supported, in the future
+                // filtering other nearby specifications will be added.
                 case ScanRequest.SCAN_TYPE_NEARBY_PRESENCE:
                     return PresenceScanFilter.createFromParcelBody(in);
                 default:
@@ -48,16 +53,16 @@
     };
 
     private final @ScanRequest.ScanType int mType;
-    private final int mRssiThreshold;
+    private final int mMaxPathLoss;
 
     /**
      * Constructs a Scan Filter.
      *
      * @hide
      */
-    ScanFilter(@ScanRequest.ScanType int type, int rssiThreshold) {
+    ScanFilter(@ScanRequest.ScanType int type, @IntRange(from = 0, to = 127) int maxPathLoss) {
         mType = type;
-        mRssiThreshold = rssiThreshold;
+        mMaxPathLoss = maxPathLoss;
     }
 
     /**
@@ -67,7 +72,7 @@
      */
     ScanFilter(@ScanRequest.ScanType int type, Parcel in) {
         mType = type;
-        mRssiThreshold = in.readInt();
+        mMaxPathLoss = in.readInt();
     }
 
     /**
@@ -78,16 +83,19 @@
     }
 
     /**
-     * Minimum RSSI of the received scan result.
+     * Returns the maximum path loss (in dBm) of the received scan result. The path loss is the
+     * attenuation of radio energy between sender and receiver. Path loss here is defined as
+     * (TxPower - Rssi).
      */
-    public int getRssiThreshold() {
-        return mRssiThreshold;
+    @IntRange(from = 0, to = 127)
+    public int getMaxPathLoss() {
+        return mMaxPathLoss;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
-        dest.writeInt(mRssiThreshold);
+        dest.writeInt(mMaxPathLoss);
     }
 
     /**
@@ -97,5 +105,4 @@
     public int describeContents() {
         return 0;
     }
-
 }
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index 9180d5e..7ff0631 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -126,7 +126,7 @@
     /**
      * Returns true if an integer is a defined scan type.
      */
-    public static boolean isValidScanType(int scanType) {
+    public static boolean isValidScanType(@ScanType int scanType) {
         return scanType == SCAN_TYPE_FAST_PAIR
                 || scanType == SCAN_TYPE_NEARBY_SHARE
                 || scanType == SCAN_TYPE_NEARBY_PRESENCE
@@ -136,7 +136,7 @@
     /**
      * Returns true if an integer is a defined scan mode.
      */
-    public static boolean isValidScanMode(int scanMode) {
+    public static boolean isValidScanMode(@ScanMode int scanMode) {
         return scanMode == SCAN_MODE_LOW_LATENCY
                 || scanMode == SCAN_MODE_BALANCED
                 || scanMode == SCAN_MODE_LOW_POWER
@@ -166,8 +166,6 @@
 
     /**
      * Returns Scan Filters for this request.
-     *
-     * @hide
      */
     @NonNull
     public List<ScanFilter> getScanFilters() {
@@ -334,11 +332,10 @@
          * usage of Nearby scans.
          *
          * @param scanFilter Filter for scanning the request.
-         *
-         * @hide
          */
         @NonNull
         public Builder addScanFilter(@NonNull ScanFilter scanFilter) {
+            Objects.requireNonNull(scanFilter);
             mScanFilters.add(scanFilter);
             return this;
         }
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl
index 565439b..ef00321 100644
--- a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl
+++ b/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl
@@ -39,6 +39,9 @@
     // The image icon that shows in the notification.
     byte[] image;
 
+    // The name of the device.
+    String name;
+
     int deviceType;
 
     // The image urls for device with device type "true wireless".
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl
new file mode 100644
index 0000000..4f666bc
--- /dev/null
+++ b/nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.aidl;
+
+import android.nearby.aidl.IFastPairStatusCallback;
+import android.nearby.FastPairDevice;
+
+/**
+ * 0p API for controlling Fast Pair. Used to talk between foreground activities
+ * and background services.
+ *
+ * {@hide}
+ */
+interface IFastPairClient {
+
+    void registerHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
+
+    void unregisterHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
+
+    void connect(in FastPairDevice fastPairDevice);
+}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl
new file mode 100644
index 0000000..d844c06
--- /dev/null
+++ b/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.aidl;
+
+import android.nearby.FastPairDevice;
+import android.nearby.PairStatusMetadata;
+
+/**
+ *
+ * Provides callbacks for Fast Pair foreground activity to learn about paring status from backend.
+ *
+ * {@hide}
+ */
+interface IFastPairStatusCallback {
+
+    /** Reports a pair status related metadata associated with a {@link FastPairDevice} */
+    void onPairUpdate(in FastPairDevice fastPairDevice, in PairStatusMetadata pairStatusMetadata);
+}
diff --git a/nearby/halfsheet/AndroidManifest.xml b/nearby/halfsheet/AndroidManifest.xml
index 3df7970..22987fb 100644
--- a/nearby/halfsheet/AndroidManifest.xml
+++ b/nearby/halfsheet/AndroidManifest.xml
@@ -20,6 +20,7 @@
         <activity
             android:name="com.android.nearby.halfsheet.HalfSheetActivity"
             android:exported="true"
+            android:launchMode="singleInstance"
             android:theme="@style/HalfSheetStyle" >
             <intent-filter>
                 <action android:name="android.nearby.SHOW_HALFSHEET"/>
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 1e1af20..8f6a227 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -20,6 +20,7 @@
     name: "nearby-service-srcs",
     srcs: [
         "java/**/*.java",
+        ":statslog-nearby-java-gen",
     ],
 }
 
@@ -53,6 +54,7 @@
         "framework-bluetooth.stubs.module_lib", // TODO(b/215722418): Change to framework-bluetooth once fixed
         "error_prone_annotations",
         "framework-connectivity-tiramisu.impl",
+        "framework-statsd.stubs.module_lib",
     ],
     static_libs: [
         "androidx.annotation_annotation",
@@ -84,3 +86,12 @@
         "com.android.tethering",
     ],
 }
+
+genrule {
+    name: "statslog-nearby-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module nearby " +
+         " --javaPackage com.android.server.nearby.proto --javaClass NearbyStatsLog" +
+         " --minApiLevel 33",
+    out: ["com/android/server/nearby/proto/NearbyStatsLog.java"],
+}
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 1477d95..5958007 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
@@ -20,6 +20,17 @@
  * String constant for half sheet.
  */
 public class Constant {
+
+    /**
+     * Value represents true for {@link android.provider.Settings.Secure}
+     */
+    public static final int SETTINGS_TRUE_VALUE = 1;
+
+    /**
+     * 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 SUCCESS_STATE = "SUCCESS";
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 672d785..f8d5a62 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -30,14 +30,12 @@
 import com.android.server.nearby.common.locator.Locator;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
+import com.android.server.nearby.util.DataUtils;
 import com.android.server.nearby.util.FastPairDecoder;
 import com.android.server.nearby.util.Hex;
 
-import com.google.protobuf.ByteString;
-
 import java.util.List;
 
-import service.proto.Cache;
 import service.proto.Data;
 import service.proto.Rpcs;
 
@@ -70,36 +68,37 @@
      */
     public void handleBroadcast(NearbyDevice device) {
         FastPairDevice fastPairDevice = (FastPairDevice) device;
-        if (mBleAddress != null && mBleAddress.equals(fastPairDevice.getBluetoothAddress())) {
-            return;
-        }
         mBleAddress = fastPairDevice.getBluetoothAddress();
         if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
             byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
             Log.d("FastPairService",
-                    "On discovery model id" + Hex.bytesToStringLowercase(model));
+                    "On discovery model id " + Hex.bytesToStringLowercase(model));
             // Use api to get anti spoofing key from model id.
             try {
                 Rpcs.GetObservedDeviceResponse response =
                         FastPairDataProvider.getInstance()
                                 .loadFastPairAntispoofkeyDeviceMetadata(model);
-                ByteString publicKey = response.getDevice().getAntiSpoofingKeyPair().getPublicKey();
                 Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
-                        Cache.ScanFastPairStoreItem.newBuilder().setAddress(mBleAddress)
-                                .setAntiSpoofingPublicKey(publicKey)
-                                .build());
+                        DataUtils.toScanFastPairStoreItem(response, mBleAddress));
             } catch (IllegalStateException e) {
                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
             }
 
         } else {
             // Start to process bloom filter
-            List<Account> accountList =
-                    FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
-            byte[] bloomFilterByteArray = FastPairDecoder.getBloomFilter(fastPairDevice.getData());
-            byte[] bloomFilterSalt = FastPairDecoder.getBloomFilterSalt(fastPairDevice.getData());
-            for (Account account : accountList) {
-                try {
+            try {
+                List<Account> accountList =
+                        FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
+                Log.d(TAG, "account list size" + accountList.size());
+                byte[] bloomFilterByteArray = FastPairDecoder
+                        .getBloomFilter(fastPairDevice.getData());
+                byte[] bloomFilterSalt = FastPairDecoder
+                        .getBloomFilterSalt(fastPairDevice.getData());
+                if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
+                    Log.d(TAG, "bloom filter byte size is 0");
+                    return;
+                }
+                for (Account account : accountList) {
                     List<Data.FastPairDeviceWithAccountKey> listDevices =
                             FastPairDataProvider.getInstance().loadFastPairDevicesWithAccountKey(
                                     account);
@@ -108,14 +107,15 @@
                                     new BloomFilter(bloomFilterByteArray,
                                             new FastPairBloomFilterHasher()), bloomFilterSalt);
                     if (recognizedDevice != null) {
-                        Log.d(TAG, "find matched device show notification to remind user to pair");
+                        Log.d(TAG, "find matched device show notification to remind"
+                                + " user to pair");
                         return;
                     }
-                } catch (IllegalStateException e) {
-                    Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
                 }
-
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
             }
+
         }
     }
 
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 cf8cd62..793e126 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
@@ -125,10 +125,7 @@
                     @Override
                     public void run() {
                         DiscoveryItem item = null;
-                        if (itemId != null) {
-                            // api call to get Fast Pair related info
-                            item = mFastPairCacheManager.getDiscoveryItem(itemId);
-                        } else if (discoveryItem != null) {
+                        if (discoveryItem != null) {
                             try {
                                 item = new DiscoveryItem(mContext,
                                         Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
@@ -136,6 +133,7 @@
                                 Log.w(TAG,
                                         "Error parsing serialized discovery item with size "
                                                 + discoveryItem.length);
+                                return;
                             }
                         }
 
@@ -196,6 +194,7 @@
             Log.d(TAG, "FastPair: fastpairing, skip pair request");
             return;
         }
+        mIsFastPairing = true;
         Log.d(TAG, "FastPair: start pair");
 
         // Hide all "tap to pair" notifications until after the flow completes.
@@ -213,7 +212,7 @@
                         companionApp,
                         mFootprintsDeviceManager,
                         pairingProgressHandlerBase);
-        mIsFastPairing = true;
+        mIsFastPairing = false;
     }
 
     /** Fixes a companion app package name with extra spaces. */
@@ -247,26 +246,26 @@
         FastPairManager.processBackgroundTask(() -> {
             Cache.StoredDiscoveryItem storedDiscoveryItem =
                     prepareStoredDiscoveryItemForFootprints(discoveryItem);
-            if (storedDiscoveryItem != null) {
-                byte[] hashValue =
-                        Hashing.sha256()
-                                .hashBytes(
-                                        concat(accountKey, BluetoothAddress.decode(publicAddress)))
-                                .asBytes();
-                FastPairUploadInfo uploadInfo =
-                        new FastPairUploadInfo(storedDiscoveryItem, ByteString.copyFrom(accountKey),
-                                ByteString.copyFrom(hashValue));
-                // account data place holder here
-                try {
-                    FastPairDataProvider.getInstance().optIn(new Account("empty", "empty"));
+            byte[] hashValue =
+                    Hashing.sha256()
+                            .hashBytes(
+                                    concat(accountKey, BluetoothAddress.decode(publicAddress)))
+                            .asBytes();
+            FastPairUploadInfo uploadInfo =
+                    new FastPairUploadInfo(storedDiscoveryItem, ByteString.copyFrom(accountKey),
+                            ByteString.copyFrom(hashValue));
+            // account data place holder here
+            try {
+                List<Account> accountList =
+                        FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
+                if (accountList.size() > 0) {
+                    FastPairDataProvider.getInstance().optIn(accountList.get(0));
                     FastPairDataProvider.getInstance().upload(
-                            new Account("empty", "empty"), uploadInfo);
-                } catch (IllegalStateException e) {
-                    Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
+                            accountList.get(0), uploadInfo);
                 }
-
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
             }
-
         });
     }
 
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 7e2151d..9e1a718 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,20 +16,27 @@
 
 package com.android.server.nearby.fastpair;
 
+import static com.android.server.nearby.fastpair.Constant.SETTINGS_TRUE_VALUE;
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.app.KeyguardManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.nearby.FastPairDevice;
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyManager;
 import android.nearby.ScanCallback;
 import android.nearby.ScanRequest;
+import android.net.Uri;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -71,47 +78,32 @@
  */
 
 public class FastPairManager {
+
     private static final String ACTION_PREFIX = UserActionHandler.PREFIX;
     private static final int WAIT_FOR_UNLOCK_MILLIS = 5000;
+
     /** 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;
 
+    private ContentObserver mFastPairScanChangeContentObserver = null;
+
     final LocatorContextWrapper mLocatorContextWrapper;
     final IntentFilter mIntentFilter;
     final Locator mLocator;
-    private boolean mAllowScan = false;
+    private boolean mScanEnabled = false;
     private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-                Log.d("FastPairService", " screen on");
-                NearbyManager nearbyManager = (NearbyManager) mLocatorContextWrapper
-                        .getApplicationContext().getSystemService(Context.NEARBY_SERVICE);
-
-                Log.d("FastPairService", " the nearby manager is " + nearbyManager);
-
-                if (nearbyManager != null) {
-                    if (mAllowScan) {
-                        nearbyManager.startScan(
-                                new ScanRequest.Builder()
-                                        .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(),
-                                ForegroundThread.getExecutor(),
-                                mScanCallback);
-                    }
-                } else {
-                    Log.d("FastPairService", " the nearby manager is null");
-                }
-
-            } else {
-                Log.d("FastPairService", " screen off");
+                Log.d(TAG, "onReceive: ACTION_SCREEN_ON.");
+                invalidateScan();
             }
         }
     };
 
-
     public FastPairManager(LocatorContextWrapper contextWrapper) {
         mLocatorContextWrapper = contextWrapper;
         mIntentFilter = new IntentFilter();
@@ -131,16 +123,14 @@
         public void onUpdated(@NonNull NearbyDevice device) {
             FastPairDevice fastPairDevice = (FastPairDevice) device;
             byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d("FastPairService",
-                    "update model id" + Hex.bytesToStringLowercase(modelArray));
+            Log.d(TAG, "update model id" + Hex.bytesToStringLowercase(modelArray));
         }
 
         @Override
         public void onLost(@NonNull NearbyDevice device) {
             FastPairDevice fastPairDevice = (FastPairDevice) device;
             byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d("FastPairService",
-                    "lost model id" + Hex.bytesToStringLowercase(modelArray));
+            Log.d(TAG, "lost model id" + Hex.bytesToStringLowercase(modelArray));
         }
     };
 
@@ -155,6 +145,13 @@
                 .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
 
         Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
+        try {
+            mScanEnabled = getScanEnabledFromSettings();
+        } catch (Settings.SettingNotFoundException e) {
+            Log.w(TAG,
+                    "initiate: Failed to get initial scan enabled status from Settings.", e);
+        }
+        registerFastPairScanChangeContentObserver(mLocatorContextWrapper.getContentResolver());
     }
 
     /**
@@ -162,6 +159,10 @@
      */
     public void cleanUp() {
         mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver);
+        if (mFastPairScanChangeContentObserver != null) {
+            mLocatorContextWrapper.getContentResolver().unregisterContentObserver(
+                    mFastPairScanChangeContentObserver);
+        }
     }
 
     /**
@@ -212,18 +213,17 @@
             boolean isBluetoothEnabled = bluetoothAdapter != null && bluetoothAdapter.isEnabled();
             if (!isBluetoothEnabled) {
                 if (bluetoothAdapter == null || !bluetoothAdapter.enable()) {
-                    Log.d("FastPairManager", "FastPair: Failed to enable bluetooth");
+                    Log.d(TAG, "FastPair: Failed to enable bluetooth");
                     return;
                 }
-                Log.v("FastPairManager", "FastPair: Enabling bluetooth for fast pair");
+                Log.v(TAG, "FastPair: Enabling bluetooth for fast pair");
 
                 Locator.get(context, EventLoop.class)
                         .postRunnable(
                                 new NamedRunnable("enableBluetoothToast") {
                                     @Override
                                     public void run() {
-                                        Log.d("FastPairManager",
-                                                "Enable bluetooth toast test");
+                                        Log.d(TAG, "Enable bluetooth toast test");
                                     }
                                 });
                 // Set up call back to call this function again once bluetooth has been
@@ -287,7 +287,7 @@
                 | ExecutionException
                 | PairingException
                 | GeneralSecurityException e) {
-            Log.e("FastPairManager", "FastPair: Error");
+            Log.e(TAG, "FastPair: Error");
             pairingProgressHandlerBase.onPairingFailed(e);
         }
     }
@@ -307,8 +307,7 @@
         // pattern) So we use this method instead, which returns true when on the lock screen
         // regardless.
         if (keyguardManager.isKeyguardLocked()) {
-            Log.v("FastPairManager",
-                    "FastPair: Screen is locked, waiting until unlocked "
+            Log.v(TAG, "FastPair: Screen is locked, waiting until unlocked "
                             + "to show status notifications.");
             try (SimpleBroadcastReceiver isUnlockedReceiver =
                          SimpleBroadcastReceiver.oneShotReceiver(
@@ -319,6 +318,28 @@
         }
     }
 
+    private void registerFastPairScanChangeContentObserver(ContentResolver resolver) {
+        mFastPairScanChangeContentObserver = new ContentObserver(ForegroundThread.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                try {
+                    setScanEnabled(getScanEnabledFromSettings());
+                } catch (Settings.SettingNotFoundException e) {
+                    Log.e(TAG, "Failed to get scan switch updates in Settings page.", e);
+                }
+            }
+        };
+        try {
+            resolver.registerContentObserver(
+                    Settings.Secure.getUriFor(Settings.Secure.FAST_PAIR_SCAN_ENABLED),
+                    /* notifyForDescendants= */ false,
+                    mFastPairScanChangeContentObserver);
+        }  catch (SecurityException e) {
+            Log.e(TAG, "Failed to register content observer for fast pair scan.", e);
+        }
+    }
+
     /**
      * Processed task in a background thread
      */
@@ -339,6 +360,49 @@
     }
 
     /**
+     * Null when the Nearby Service is not available.
+     */
+    @Nullable
+    private NearbyManager getNearbyManager() {
+        return (NearbyManager) mLocatorContextWrapper
+                .getApplicationContext().getSystemService(Context.NEARBY_SERVICE);
+    }
+
+    private boolean getScanEnabledFromSettings() throws Settings.SettingNotFoundException {
+        return Settings.Secure.getInt(
+                mLocatorContextWrapper.getContext().getContentResolver(),
+                Settings.Secure.FAST_PAIR_SCAN_ENABLED) == SETTINGS_TRUE_VALUE;
+    }
+
+    private void setScanEnabled(boolean scanEnabled) {
+        if (mScanEnabled == scanEnabled) {
+            return;
+        }
+        mScanEnabled = scanEnabled;
+        invalidateScan();
+    }
+
+    /**
+     *  Starts or stops scanning according to mAllowScan value.
+     */
+    private void invalidateScan() {
+        NearbyManager nearbyManager = getNearbyManager();
+        if (nearbyManager == null) {
+            Log.w(TAG, "invalidateScan: "
+                    + "failed to start or stop scannning because NearbyManager is null.");
+            return;
+        }
+        if (mScanEnabled) {
+            nearbyManager.startScan(new ScanRequest.Builder()
+                            .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(),
+                    ForegroundThread.getExecutor(),
+                    mScanCallback);
+        } else {
+            nearbyManager.stopScan(mScanCallback);
+        }
+    }
+
+    /**
      * Helper function to get bluetooth adapter.
      */
     @Nullable
@@ -346,5 +410,4 @@
         BluetoothManager manager = context.getSystemService(BluetoothManager.class);
         return manager == null ? null : manager.getAdapter();
     }
-
 }
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 9fdd01a..cfac904 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
@@ -48,6 +48,8 @@
  */
 public class FastPairHalfSheetManager {
     private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
+    private static final String HALF_SHEET_CLASS_NAME =
+            "com.android.nearby.halfsheet.HalfSheetActivity";
 
     private String mHalfSheetApkPkgName;
     private Context mContext;
@@ -65,22 +67,28 @@
      * app can't get the correct component name.
      */
     public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
-        if (mContext != null) {
-            String packageName = getHalfSheetApkPkgName();
-            HalfSheetCallback callback = new HalfSheetCallback();
-            callback.setmFastPairController(Locator.get(mContext, FastPairController.class));
-            Bundle bundle = new Bundle();
-            bundle.putBinder(EXTRA_BINDER, callback);
-            mContext
-                    .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
-                                    .putExtra(EXTRA_HALF_SHEET_INFO,
-                                            scanFastPairStoreItem.toByteArray())
-                                    .putExtra(EXTRA_HALF_SHEET_TYPE, DEVICE_PAIRING_FRAGMENT_TYPE)
-                                    .putExtra(EXTRA_BUNDLE, bundle)
-                                    .setComponent(new ComponentName(packageName,
-                                            packageName + ".HalfSheetActivity")),
-                            UserHandle.CURRENT);
+        try {
+            if (mContext != null) {
+                String packageName = getHalfSheetApkPkgName();
+                HalfSheetCallback callback = new HalfSheetCallback();
+                callback.setmFastPairController(Locator.get(mContext, FastPairController.class));
+                Bundle bundle = new Bundle();
+                bundle.putBinder(EXTRA_BINDER, callback);
+                mContext
+                        .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
+                                        .putExtra(EXTRA_HALF_SHEET_INFO,
+                                                scanFastPairStoreItem.toByteArray())
+                                        .putExtra(EXTRA_HALF_SHEET_TYPE,
+                                                DEVICE_PAIRING_FRAGMENT_TYPE)
+                                        .putExtra(EXTRA_BUNDLE, bundle)
+                                        .setComponent(new ComponentName(packageName,
+                                                HALF_SHEET_CLASS_NAME)),
+                                UserHandle.CURRENT);
 
+            }
+        } catch (IllegalStateException e) {
+            Log.e("FastPairHalfSheetManager",
+                    "Can't resolve package that contains half sheet");
         }
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/metrics/NearbyMetrics.java b/nearby/service/java/com/android/server/nearby/metrics/NearbyMetrics.java
new file mode 100644
index 0000000..75815f1
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/metrics/NearbyMetrics.java
@@ -0,0 +1,85 @@
+/*
+ * 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.metrics;
+
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+import android.os.WorkSource;
+
+import com.android.server.nearby.proto.NearbyStatsLog;
+
+/**
+ * A class to collect and report Nearby metrics.
+ */
+public class NearbyMetrics {
+    /**
+     * Logs a scan started event.
+     */
+    public static void logScanStarted(int scanSessionId, ScanRequest scanRequest) {
+        NearbyStatsLog.write(
+                NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+                getUid(scanRequest),
+                scanSessionId,
+                NearbyStatsLog
+                        .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STARTED,
+                scanRequest.getScanType(),
+                0,
+                0,
+                "",
+                "");
+    }
+
+    /**
+     * Logs a scan stopped event.
+     */
+    public static void logScanStopped(int scanSessionId, ScanRequest scanRequest) {
+        NearbyStatsLog.write(
+                NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+                getUid(scanRequest),
+                scanSessionId,
+                NearbyStatsLog
+                        .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STOPPED,
+                scanRequest.getScanType(),
+                0,
+                0,
+                "",
+                "");
+    }
+
+    /**
+     * Logs a scan device discovered event.
+     */
+    public static void logScanDeviceDiscovered(int scanSessionId, ScanRequest scanRequest,
+            NearbyDeviceParcelable nearbyDevice) {
+        NearbyStatsLog.write(
+                NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+                getUid(scanRequest),
+                scanSessionId,
+                NearbyStatsLog
+                        .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_DISCOVERED,
+                scanRequest.getScanType(),
+                nearbyDevice.getMedium(),
+                nearbyDevice.getRssi(),
+                nearbyDevice.getFastPairModelId(),
+                "");
+    }
+
+    private static int getUid(ScanRequest scanRequest) {
+        WorkSource workSource = scanRequest.getWorkSource();
+        return workSource.isEmpty() ? -1 : workSource.getUid(0);
+    }
+}
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 72c4b75..27f3acb 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.metrics.NearbyMetrics;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -59,6 +60,8 @@
                     record.getScanListener().onDiscovered(
                             PrivacyFilter.filter(record.getScanRequest().getScanType(),
                                     nearbyDevice));
+                    NearbyMetrics.logScanDeviceDiscovered(
+                            record.hashCode(), record.getScanRequest(), nearbyDevice);
                 } catch (RemoteException e) {
                     Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e);
                 }
@@ -89,8 +92,9 @@
 
             startProviders(scanRequest);
 
-            mScanTypeScanListenerRecordMap.put(listenerBinder,
-                    new ScanListenerRecord(scanRequest, listener));
+            ScanListenerRecord scanListenerRecord = new ScanListenerRecord(scanRequest, listener);
+            mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
+            NearbyMetrics.logScanStarted(scanListenerRecord.hashCode(), scanRequest);
             if (mScanMode < scanRequest.getScanMode()) {
                 mScanMode = scanRequest.getScanMode();
                 invalidateProviderScanMode();
@@ -113,6 +117,8 @@
 
             ScanListenerRecord removedRecord = mScanTypeScanListenerRecordMap
                     .remove(listenerBinder);
+            NearbyMetrics.logScanStopped(
+                    removedRecord.hashCode(), removedRecord.getScanRequest());
             if (mScanTypeScanListenerRecordMap.isEmpty()) {
                 stopProviders();
                 return;
@@ -201,7 +207,7 @@
 
         @Override
         public int hashCode() {
-            return  Objects.hash(mScanListener, mScanRequest);
+            return Objects.hash(mScanListener, mScanRequest);
         }
     }
 }
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 f8c16a1..40fefaf 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -90,7 +90,7 @@
             FastPairAntispoofkeyDeviceMetadataRequestParcel requestParcel =
                     new FastPairAntispoofkeyDeviceMetadataRequestParcel();
             requestParcel.modelId = modelId;
-            return Utils.convertFastPairAntispoofkeyDeviceMetadataToGetObservedDeviceResponse(
+            return Utils.convertToGetObservedDeviceResponse(
                     mProxyFastPairDataProvider
                             .loadFastPairAntispoofkeyDeviceMetadata(requestParcel));
         }
@@ -109,6 +109,7 @@
             requestParcel.account = account;
             requestParcel.requestType = FastPairDataProviderBase.MANAGE_REQUEST_ADD;
             mProxyFastPairDataProvider.manageFastPairAccount(requestParcel);
+            return;
         }
         throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
     }
@@ -125,9 +126,9 @@
             requestParcel.account = account;
             requestParcel.requestType = FastPairDataProviderBase.MANAGE_REQUEST_ADD;
             requestParcel.accountKeyDeviceMetadata =
-                    Utils.convertFastPairUploadInfoToFastPairAccountKeyDeviceMetadata(
-                            uploadInfo);
+                    Utils.convertToFastPairAccountKeyDeviceMetadata(uploadInfo);
             mProxyFastPairDataProvider.manageFastPairAccountDevice(requestParcel);
+            return;
         }
         throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
     }
@@ -151,7 +152,7 @@
             FastPairAccountDevicesMetadataRequestParcel requestParcel =
                     new FastPairAccountDevicesMetadataRequestParcel();
             requestParcel.account = account;
-            return Utils.convertFastPairAccountKeyDevicesMetadataToFastPairDevicesWithAccountKey(
+            return Utils.convertToFastPairDevicesWithAccountKey(
                     mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(requestParcel));
         }
         throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
@@ -166,7 +167,7 @@
         if (mProxyFastPairDataProvider != null) {
             FastPairEligibleAccountsRequestParcel requestParcel =
                     new FastPairEligibleAccountsRequestParcel();
-            return Utils.convertFastPairEligibleAccountsToAccountList(
+            return Utils.convertToAccountList(
                     mProxyFastPairDataProvider.loadFastPairEligibleAccounts(requestParcel));
         }
         throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
diff --git a/nearby/service/java/com/android/server/nearby/provider/Utils.java b/nearby/service/java/com/android/server/nearby/provider/Utils.java
index a340b04..b84dfe9 100644
--- a/nearby/service/java/com/android/server/nearby/provider/Utils.java
+++ b/nearby/service/java/com/android/server/nearby/provider/Utils.java
@@ -41,9 +41,8 @@
  */
 class Utils {
 
-    static List<Data.FastPairDeviceWithAccountKey>
-            convertFastPairAccountKeyDevicesMetadataToFastPairDevicesWithAccountKey(
-                    @Nullable FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
+    static List<Data.FastPairDeviceWithAccountKey> convertToFastPairDevicesWithAccountKey(
+            @Nullable FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
         if (metadataParcels == null) {
             return new ArrayList<Data.FastPairDeviceWithAccountKey>(0);
         }
@@ -51,6 +50,9 @@
         List<Data.FastPairDeviceWithAccountKey> fpDeviceList =
                 new ArrayList<>(metadataParcels.length);
         for (FastPairAccountKeyDeviceMetadataParcel metadataParcel : metadataParcels) {
+            if (metadataParcel == null) {
+                continue;
+            }
             Data.FastPairDeviceWithAccountKey.Builder fpDeviceBuilder =
                     Data.FastPairDeviceWithAccountKey.newBuilder();
             if (metadataParcel.accountKey != null) {
@@ -68,15 +70,20 @@
                 if (metadataParcel.discoveryItem.actionUrl != null) {
                     storedDiscoveryItemBuilder.setActionUrl(metadataParcel.discoveryItem.actionUrl);
                 }
-                storedDiscoveryItemBuilder.setActionUrlType(
-                        Cache.ResolvedUrlType.forNumber(
-                                metadataParcel.discoveryItem.actionUrlType));
+                Cache.ResolvedUrlType urlType = Cache.ResolvedUrlType.forNumber(
+                        metadataParcel.discoveryItem.actionUrlType);
+                if (urlType != null) {
+                    storedDiscoveryItemBuilder.setActionUrlType(urlType);
+                }
                 if (metadataParcel.discoveryItem.appName != null) {
                     storedDiscoveryItemBuilder.setAppName(metadataParcel.discoveryItem.appName);
                 }
-                storedDiscoveryItemBuilder.setAttachmentType(
+                Cache.DiscoveryAttachmentType attachmentType =
                         Cache.DiscoveryAttachmentType.forNumber(
-                                metadataParcel.discoveryItem.attachmentType));
+                                metadataParcel.discoveryItem.attachmentType);
+                if (attachmentType != null) {
+                    storedDiscoveryItemBuilder.setAttachmentType(attachmentType);
+                }
                 if (metadataParcel.discoveryItem.authenticationPublicKeySecp256r1 != null) {
                     storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
                             ByteString.copyFrom(
@@ -86,9 +93,12 @@
                     storedDiscoveryItemBuilder.setBleRecordBytes(
                             ByteString.copyFrom(metadataParcel.discoveryItem.bleRecordBytes));
                 }
-                storedDiscoveryItemBuilder.setDebugCategory(
+                Cache.StoredDiscoveryItem.DebugMessageCategory debugMessageCategory =
                         Cache.StoredDiscoveryItem.DebugMessageCategory.forNumber(
-                                metadataParcel.discoveryItem.debugCategory));
+                                metadataParcel.discoveryItem.debugCategory);
+                if (debugMessageCategory != null) {
+                    storedDiscoveryItemBuilder.setDebugCategory(debugMessageCategory);
+                }
                 if (metadataParcel.discoveryItem.debugMessage != null) {
                     storedDiscoveryItemBuilder.setDebugMessage(
                             metadataParcel.discoveryItem.debugMessage);
@@ -131,9 +141,12 @@
                 }
                 storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
                         metadataParcel.discoveryItem.lastObservationTimestampMillis);
-                storedDiscoveryItemBuilder.setLastUserExperience(
+                Cache.StoredDiscoveryItem.ExperienceType experienceType =
                         Cache.StoredDiscoveryItem.ExperienceType.forNumber(
-                                metadataParcel.discoveryItem.lastUserExperience));
+                                metadataParcel.discoveryItem.lastUserExperience);
+                if (experienceType != null) {
+                    storedDiscoveryItemBuilder.setLastUserExperience(experienceType);
+                }
                 storedDiscoveryItemBuilder.setLostMillis(metadataParcel.discoveryItem.lostMillis);
                 if (metadataParcel.discoveryItem.macAddress != null) {
                     storedDiscoveryItemBuilder.setMacAddress(
@@ -146,9 +159,12 @@
                 storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis(
                         metadataParcel.discoveryItem.pendingAppInstallTimestampMillis);
                 storedDiscoveryItemBuilder.setRssi(metadataParcel.discoveryItem.rssi);
-                storedDiscoveryItemBuilder.setState(
+                Cache.StoredDiscoveryItem.State state =
                         Cache.StoredDiscoveryItem.State.forNumber(
-                                metadataParcel.discoveryItem.state));
+                                metadataParcel.discoveryItem.state);
+                if (state != null) {
+                    storedDiscoveryItemBuilder.setState(state);
+                }
                 if (metadataParcel.discoveryItem.title != null) {
                     storedDiscoveryItemBuilder.setTitle(metadataParcel.discoveryItem.title);
                 }
@@ -156,8 +172,11 @@
                     storedDiscoveryItemBuilder.setTriggerId(metadataParcel.discoveryItem.triggerId);
                 }
                 storedDiscoveryItemBuilder.setTxPower(metadataParcel.discoveryItem.txPower);
-                storedDiscoveryItemBuilder.setType(
-                        Cache.NearbyType.forNumber(metadataParcel.discoveryItem.type));
+                Cache.NearbyType type =
+                        Cache.NearbyType.forNumber(metadataParcel.discoveryItem.type);
+                if (type != null) {
+                    storedDiscoveryItemBuilder.setType(type);
+                }
             }
             if (metadataParcel.metadata != null) {
                 FastPairStrings.Builder stringsBuilder = FastPairStrings.newBuilder();
@@ -250,22 +269,13 @@
                             metadataParcel.metadata.trueWirelessImageUrlRightBud);
                 }
                 fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build());
-                fpInformationBuilder.setDeviceType(
-                        Rpcs.DeviceType.forNumber(metadataParcel.metadata.deviceType));
+                Rpcs.DeviceType deviceType =
+                        Rpcs.DeviceType.forNumber(metadataParcel.metadata.deviceType);
+                if (deviceType != null) {
+                    fpInformationBuilder.setDeviceType(deviceType);
+                }
 
                 storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build());
-                storedDiscoveryItemBuilder.setTxPower(metadataParcel.metadata.bleTxPower);
-
-                if (metadataParcel.metadata.image != null) {
-                    storedDiscoveryItemBuilder.setIconPng(
-                            ByteString.copyFrom(metadataParcel.metadata.image));
-                }
-                if (metadataParcel.metadata.imageUrl != null) {
-                    storedDiscoveryItemBuilder.setIconFifeUrl(metadataParcel.metadata.imageUrl);
-                }
-                if (metadataParcel.metadata.intentUri != null) {
-                    storedDiscoveryItemBuilder.setActionUrl(metadataParcel.metadata.intentUri);
-                }
             }
             fpDeviceBuilder.setDiscoveryItem(storedDiscoveryItemBuilder.build());
             fpDeviceList.add(fpDeviceBuilder.build());
@@ -273,126 +283,230 @@
         return fpDeviceList;
     }
 
-    static List<Account> convertFastPairEligibleAccountsToAccountList(
+    static List<Account> convertToAccountList(
             @Nullable FastPairEligibleAccountParcel[] accountParcels) {
         if (accountParcels == null) {
             return new ArrayList<Account>(0);
         }
         List<Account> accounts = new ArrayList<Account>(accountParcels.length);
         for (FastPairEligibleAccountParcel parcel : accountParcels) {
-            accounts.add(parcel.account);
+            if (parcel != null && parcel.account != null) {
+                accounts.add(parcel.account);
+            }
         }
         return accounts;
     }
 
+    private static @Nullable Rpcs.Device convertToDevice(
+            FastPairAntispoofkeyDeviceMetadataParcel metadata) {
+
+        Rpcs.Device.Builder deviceBuilder = Rpcs.Device.newBuilder();
+        if (metadata.antiSpoofPublicKey != null) {
+            deviceBuilder.setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder()
+                    .setPublicKey(ByteString.copyFrom(metadata.antiSpoofPublicKey))
+                    .build());
+        }
+        if (metadata.deviceMetadata != null) {
+            Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
+                    Rpcs.TrueWirelessHeadsetImages.newBuilder();
+            if (metadata.deviceMetadata.trueWirelessImageUrlLeftBud != null) {
+                imagesBuilder.setLeftBudUrl(metadata.deviceMetadata.trueWirelessImageUrlLeftBud);
+            }
+            if (metadata.deviceMetadata.trueWirelessImageUrlRightBud != null) {
+                imagesBuilder.setRightBudUrl(metadata.deviceMetadata.trueWirelessImageUrlRightBud);
+            }
+            if (metadata.deviceMetadata.trueWirelessImageUrlCase != null) {
+                imagesBuilder.setCaseUrl(metadata.deviceMetadata.trueWirelessImageUrlCase);
+            }
+            deviceBuilder.setTrueWirelessImages(imagesBuilder.build());
+            if (metadata.deviceMetadata.imageUrl != null) {
+                deviceBuilder.setImageUrl(metadata.deviceMetadata.imageUrl);
+            }
+            if (metadata.deviceMetadata.intentUri != null) {
+                deviceBuilder.setIntentUri(metadata.deviceMetadata.intentUri);
+            }
+            if (metadata.deviceMetadata.name != null) {
+                deviceBuilder.setName(metadata.deviceMetadata.name);
+            }
+            Rpcs.DeviceType deviceType =
+                    Rpcs.DeviceType.forNumber(metadata.deviceMetadata.deviceType);
+            if (deviceType != null) {
+                deviceBuilder.setDeviceType(deviceType);
+            }
+            deviceBuilder.setBleTxPower(metadata.deviceMetadata.bleTxPower)
+                    .setTriggerDistance(metadata.deviceMetadata.triggerDistance);
+        }
+
+        return deviceBuilder.build();
+    }
+
+    private static @Nullable ByteString convertToImage(
+            FastPairAntispoofkeyDeviceMetadataParcel metadata) {
+        if (metadata.deviceMetadata == null || metadata.deviceMetadata.image == null) {
+            return null;
+        }
+
+        return ByteString.copyFrom(metadata.deviceMetadata.image);
+    }
+
+    private static @Nullable Rpcs.ObservedDeviceStrings
+            convertToObservedDeviceStrings(FastPairAntispoofkeyDeviceMetadataParcel metadata) {
+        if (metadata.deviceMetadata == null) {
+            return null;
+        }
+
+        Rpcs.ObservedDeviceStrings.Builder stringsBuilder = Rpcs.ObservedDeviceStrings.newBuilder();
+        if (metadata.deviceMetadata.assistantSetupHalfSheet != null) {
+            stringsBuilder
+                    .setAssistantSetupHalfSheet(metadata.deviceMetadata.assistantSetupHalfSheet);
+        }
+        if (metadata.deviceMetadata.assistantSetupNotification != null) {
+            stringsBuilder.setAssistantSetupNotification(
+                    metadata.deviceMetadata.assistantSetupNotification);
+        }
+        if (metadata.deviceMetadata.confirmPinDescription != null) {
+            stringsBuilder.setConfirmPinDescription(metadata.deviceMetadata.confirmPinDescription);
+        }
+        if (metadata.deviceMetadata.confirmPinTitle != null) {
+            stringsBuilder.setConfirmPinTitle(metadata.deviceMetadata.confirmPinTitle);
+        }
+        if (metadata.deviceMetadata.connectSuccessCompanionAppInstalled != null) {
+            stringsBuilder.setConnectSuccessCompanionAppInstalled(
+                    metadata.deviceMetadata.connectSuccessCompanionAppInstalled);
+        }
+        if (metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled != null) {
+            stringsBuilder.setConnectSuccessCompanionAppNotInstalled(
+                    metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled);
+        }
+        if (metadata.deviceMetadata.downloadCompanionAppDescription != null) {
+            stringsBuilder.setDownloadCompanionAppDescription(
+                    metadata.deviceMetadata.downloadCompanionAppDescription);
+        }
+        if (metadata.deviceMetadata.failConnectGoToSettingsDescription != null) {
+            stringsBuilder.setFailConnectGoToSettingsDescription(
+                    metadata.deviceMetadata.failConnectGoToSettingsDescription);
+        }
+        if (metadata.deviceMetadata.fastPairTvConnectDeviceNoAccountDescription != null) {
+            stringsBuilder.setFastPairTvConnectDeviceNoAccountDescription(
+                    metadata.deviceMetadata.fastPairTvConnectDeviceNoAccountDescription);
+        }
+        if (metadata.deviceMetadata.initialNotificationDescription != null) {
+            stringsBuilder.setInitialNotificationDescription(
+                    metadata.deviceMetadata.initialNotificationDescription);
+        }
+        if (metadata.deviceMetadata.initialNotificationDescriptionNoAccount != null) {
+            stringsBuilder.setInitialNotificationDescriptionNoAccount(
+                    metadata.deviceMetadata.initialNotificationDescriptionNoAccount);
+        }
+        if (metadata.deviceMetadata.initialPairingDescription != null) {
+            stringsBuilder.setInitialPairingDescription(
+                    metadata.deviceMetadata.initialPairingDescription);
+        }
+        if (metadata.deviceMetadata.locale != null) {
+            stringsBuilder.setLocale(metadata.deviceMetadata.locale);
+        }
+        if (metadata.deviceMetadata.openCompanionAppDescription != null) {
+            stringsBuilder.setOpenCompanionAppDescription(
+                    metadata.deviceMetadata.openCompanionAppDescription);
+        }
+        if (metadata.deviceMetadata.retroactivePairingDescription != null) {
+            stringsBuilder.setRetroactivePairingDescription(
+                    metadata.deviceMetadata.retroactivePairingDescription);
+        }
+        if (metadata.deviceMetadata.subsequentPairingDescription != null) {
+            stringsBuilder.setSubsequentPairingDescription(
+                    metadata.deviceMetadata.subsequentPairingDescription);
+        }
+        if (metadata.deviceMetadata.syncContactsDescription != null) {
+            stringsBuilder.setSyncContactsDescription(
+                    metadata.deviceMetadata.syncContactsDescription);
+        }
+        if (metadata.deviceMetadata.syncContactsTitle != null) {
+            stringsBuilder.setSyncContactsTitle(
+                    metadata.deviceMetadata.syncContactsTitle);
+        }
+        if (metadata.deviceMetadata.syncSmsDescription != null) {
+            stringsBuilder.setSyncSmsDescription(
+                    metadata.deviceMetadata.syncSmsDescription);
+        }
+        if (metadata.deviceMetadata.syncSmsTitle != null) {
+            stringsBuilder.setSyncSmsTitle(
+                    metadata.deviceMetadata.syncSmsTitle);
+        }
+        if (metadata.deviceMetadata.unableToConnectDescription != null) {
+            stringsBuilder.setUnableToConnectDescription(
+                    metadata.deviceMetadata.unableToConnectDescription);
+        }
+        if (metadata.deviceMetadata.unableToConnectTitle != null) {
+            stringsBuilder.setUnableToConnectTitle(
+                    metadata.deviceMetadata.unableToConnectTitle);
+        }
+        if (metadata.deviceMetadata.updateCompanionAppDescription != null) {
+            stringsBuilder.setUpdateCompanionAppDescription(
+                    metadata.deviceMetadata.updateCompanionAppDescription);
+        }
+        if (metadata.deviceMetadata.waitLaunchCompanionAppDescription != null) {
+            stringsBuilder.setWaitLaunchCompanionAppDescription(
+                    metadata.deviceMetadata.waitLaunchCompanionAppDescription);
+        }
+
+        return stringsBuilder.build();
+    }
+
     static @Nullable Rpcs.GetObservedDeviceResponse
-            convertFastPairAntispoofkeyDeviceMetadataToGetObservedDeviceResponse(
+            convertToGetObservedDeviceResponse(
                     @Nullable FastPairAntispoofkeyDeviceMetadataParcel metadata) {
         if (metadata == null) {
             return null;
         }
-        return Rpcs.GetObservedDeviceResponse.newBuilder()
-                .setDevice(Rpcs.Device.newBuilder()
-                        .setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder()
-                                .setPublicKey(ByteString.copyFrom(metadata.antiSpoofPublicKey))
-                                .build())
-                        .setTrueWirelessImages(Rpcs.TrueWirelessHeadsetImages.newBuilder()
-                                .setLeftBudUrl(
-                                        metadata.deviceMetadata.trueWirelessImageUrlLeftBud)
-                                .setRightBudUrl(
-                                        metadata.deviceMetadata
-                                                .trueWirelessImageUrlRightBud)
-                                .setCaseUrl(
-                                        metadata.deviceMetadata
-                                                .trueWirelessImageUrlCase
-                                )
-                                .build())
-                        .setImageUrl(metadata.deviceMetadata.imageUrl)
-                        .setIntentUri(metadata.deviceMetadata.intentUri)
-                        .setBleTxPower(metadata.deviceMetadata.bleTxPower)
-                        .setTriggerDistance(metadata.deviceMetadata.triggerDistance)
-                        .setDeviceType(
-                                Rpcs.DeviceType.forNumber(metadata.deviceMetadata.deviceType))
-                        .build())
-                .setImage(ByteString.copyFrom(metadata.deviceMetadata.image))
-                .setStrings(Rpcs.ObservedDeviceStrings.newBuilder()
-                        .setAssistantSetupHalfSheet(metadata.deviceMetadata.assistantSetupHalfSheet)
-                        .setAssistantSetupNotification(
-                                metadata.deviceMetadata.assistantSetupNotification)
-                        .setConfirmPinDescription(metadata.deviceMetadata.confirmPinDescription)
-                        .setConfirmPinTitle(metadata.deviceMetadata.confirmPinTitle)
-                        .setConnectSuccessCompanionAppInstalled(
-                                metadata.deviceMetadata.connectSuccessCompanionAppInstalled)
-                        .setConnectSuccessCompanionAppNotInstalled(
-                                metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled)
-                        .setDownloadCompanionAppDescription(
-                                metadata.deviceMetadata.downloadCompanionAppDescription)
-                        .setFailConnectGoToSettingsDescription(
-                                metadata.deviceMetadata.failConnectGoToSettingsDescription)
-                        .setFastPairTvConnectDeviceNoAccountDescription(
-                                metadata.deviceMetadata.fastPairTvConnectDeviceNoAccountDescription)
-                        .setInitialNotificationDescription(
-                                metadata.deviceMetadata.initialNotificationDescription)
-                        .setInitialNotificationDescriptionNoAccount(
-                                metadata.deviceMetadata.initialNotificationDescriptionNoAccount)
-                        .setInitialPairingDescription(
-                                metadata.deviceMetadata.initialPairingDescription)
-                        .setLocale(metadata.deviceMetadata.locale)
-                        .setOpenCompanionAppDescription(
-                                metadata.deviceMetadata.openCompanionAppDescription)
-                        .setRetroactivePairingDescription(
-                                metadata.deviceMetadata.retroactivePairingDescription)
-                        .setSubsequentPairingDescription(
-                                metadata.deviceMetadata.subsequentPairingDescription)
-                        .setSyncContactsDescription(
-                                metadata.deviceMetadata.syncContactsDescription)
-                        .setSyncContactsTitle(
-                                metadata.deviceMetadata.syncContactsTitle)
-                        .setSyncSmsDescription(
-                                metadata.deviceMetadata.syncSmsDescription)
-                        .setSyncSmsTitle(
-                                metadata.deviceMetadata.syncSmsTitle)
-                        .setUnableToConnectDescription(
-                                metadata.deviceMetadata.unableToConnectDescription)
-                        .setUnableToConnectTitle(
-                                metadata.deviceMetadata.unableToConnectTitle)
-                        .setUpdateCompanionAppDescription(
-                                metadata.deviceMetadata.updateCompanionAppDescription)
-                        .setWaitLaunchCompanionAppDescription(
-                                metadata.deviceMetadata.waitLaunchCompanionAppDescription)
-                        .build())
-                .build();
+
+        Rpcs.GetObservedDeviceResponse.Builder responseBuilder =
+                Rpcs.GetObservedDeviceResponse.newBuilder();
+
+        Rpcs.Device device = convertToDevice(metadata);
+        if (device != null) {
+            responseBuilder.setDevice(device);
+        }
+        ByteString image = convertToImage(metadata);
+        if (image != null) {
+            responseBuilder.setImage(image);
+        }
+        Rpcs.ObservedDeviceStrings strings = convertToObservedDeviceStrings(metadata);
+        if (strings != null) {
+            responseBuilder.setStrings(strings);
+        }
+
+        return responseBuilder.build();
     }
 
     static @Nullable FastPairAccountKeyDeviceMetadataParcel
-            convertFastPairUploadInfoToFastPairAccountKeyDeviceMetadata(
-                    FastPairUploadInfo uploadInfo) {
+            convertToFastPairAccountKeyDeviceMetadata(
+            @Nullable FastPairUploadInfo uploadInfo) {
         if (uploadInfo == null) {
             return null;
         }
 
         FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadataParcel =
                 new FastPairAccountKeyDeviceMetadataParcel();
-        accountKeyDeviceMetadataParcel.accountKey = uploadInfo.getAccountKey().toByteArray();
-        accountKeyDeviceMetadataParcel.sha256AccountKeyPublicAddress =
-                uploadInfo.getSha256AccountKeyPublicAddress().toByteArray();
-        accountKeyDeviceMetadataParcel.metadata =
-                convertStoredDiscoveryItemToFastPairDeviceMetadata(
-                        uploadInfo.getStoredDiscoveryItem());
-        accountKeyDeviceMetadataParcel.discoveryItem =
-                convertStoredDiscoveryItemToFastPairDiscoveryItem(
-                        uploadInfo.getStoredDiscoveryItem());
+        if (uploadInfo.getAccountKey() != null) {
+            accountKeyDeviceMetadataParcel.accountKey = uploadInfo.getAccountKey().toByteArray();
+        }
+        if (uploadInfo.getSha256AccountKeyPublicAddress() != null) {
+            accountKeyDeviceMetadataParcel.sha256AccountKeyPublicAddress =
+                    uploadInfo.getSha256AccountKeyPublicAddress().toByteArray();
+        }
+        if (uploadInfo.getStoredDiscoveryItem() != null) {
+            accountKeyDeviceMetadataParcel.metadata =
+                    convertToFastPairDeviceMetadata(uploadInfo.getStoredDiscoveryItem());
+            accountKeyDeviceMetadataParcel.discoveryItem =
+                    convertToFastPairDiscoveryItem(uploadInfo.getStoredDiscoveryItem());
+        }
 
         return accountKeyDeviceMetadataParcel;
     }
 
     private static @Nullable FastPairDiscoveryItemParcel
-            convertStoredDiscoveryItemToFastPairDiscoveryItem(
-                    @Nullable Cache.StoredDiscoveryItem storedDiscoveryItem) {
-        if (storedDiscoveryItem == null) {
-            return null;
-        }
-
+            convertToFastPairDiscoveryItem(Cache.StoredDiscoveryItem storedDiscoveryItem) {
         FastPairDiscoveryItemParcel discoveryItemParcel = new FastPairDiscoveryItemParcel();
         discoveryItemParcel.actionUrl = storedDiscoveryItem.getActionUrl();
         discoveryItemParcel.actionUrlType = storedDiscoveryItem.getActionUrlType().getNumber();
@@ -444,12 +558,7 @@
         String updateCompanionAppDescription = bundle.getString("updateCompanionAppDescription");
     */
     private static @Nullable FastPairDeviceMetadataParcel
-            convertStoredDiscoveryItemToFastPairDeviceMetadata(
-                    @Nullable Cache.StoredDiscoveryItem storedDiscoveryItem) {
-        if (storedDiscoveryItem == null) {
-            return null;
-        }
-
+            convertToFastPairDeviceMetadata(Cache.StoredDiscoveryItem storedDiscoveryItem) {
         FastPairStrings fpStrings = storedDiscoveryItem.getFastPairStrings();
 
         FastPairDeviceMetadataParcel metadataParcel = new FastPairDeviceMetadataParcel();
@@ -485,11 +594,6 @@
                 fpInformation.getTrueWirelessImages().getRightBudUrl();
         metadataParcel.deviceType = fpInformation.getDeviceType().getNumber();
 
-        metadataParcel.bleTxPower = storedDiscoveryItem.getTxPower();
-        metadataParcel.image = storedDiscoveryItem.getIconPng().toByteArray();
-        metadataParcel.imageUrl = storedDiscoveryItem.getIconFifeUrl();
-        metadataParcel.intentUri = storedDiscoveryItem.getActionUrl();
-
         return metadataParcel;
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
new file mode 100644
index 0000000..ce738c8
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/DataUtils.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.util;
+
+import androidx.annotation.NonNull;
+
+import service.proto.Cache.ScanFastPairStoreItem;
+import service.proto.Cache.StoredDiscoveryItem;
+import service.proto.FastPairString.FastPairStrings;
+import service.proto.Rpcs.Device;
+import service.proto.Rpcs.GetObservedDeviceResponse;
+import service.proto.Rpcs.ObservedDeviceStrings;
+
+/**
+ * Utils class converts different data types {@link ScanFastPairStoreItem},
+ * {@link StoredDiscoveryItem} and {@link GetObservedDeviceResponse},
+ *
+ */
+public final class DataUtils {
+
+    /**
+     * Converts a {@link GetObservedDeviceResponse} to a {@link ScanFastPairStoreItem}.
+     */
+    public static ScanFastPairStoreItem toScanFastPairStoreItem(
+            GetObservedDeviceResponse observedDeviceResponse, @NonNull String bleAddress) {
+        Device device = observedDeviceResponse.getDevice();
+        return ScanFastPairStoreItem.newBuilder()
+                .setAddress(bleAddress)
+                .setActionUrl(device.getIntentUri())
+                .setDeviceName(device.getName())
+                .setIconPng(observedDeviceResponse.getImage())
+                .setIconFifeUrl(device.getImageUrl())
+                .setAntiSpoofingPublicKey(device.getAntiSpoofingKeyPair().getPublicKey())
+                .setFastPairStrings(getFastPairStrings(observedDeviceResponse))
+                .build();
+    }
+
+    /**
+     * Prints readable string for a {@link FastPairStrings}
+     */
+    public static String toString(FastPairStrings fastPairStrings) {
+        return "FastPairStrings["
+                + "tapToPairWithAccount=" + fastPairStrings.getTapToPairWithAccount()
+                + ", tapToPairWithoutAccount=" + fastPairStrings.getTapToPairWithoutAccount()
+                + ", initialPairingDescription=" + fastPairStrings.getInitialPairingDescription()
+                + ", pairingFinishedCompanionAppInstalled="
+                + fastPairStrings.getPairingFinishedCompanionAppInstalled()
+                + ", pairingFinishedCompanionAppNotInstalled="
+                + fastPairStrings.getPairingFinishedCompanionAppNotInstalled()
+                + ", subsequentPairingDescription="
+                + fastPairStrings.getSubsequentPairingDescription()
+                + ", retroactivePairingDescription="
+                + fastPairStrings.getRetroactivePairingDescription()
+                + ", waitAppLaunchDescription=" + fastPairStrings.getWaitAppLaunchDescription()
+                + ", pairingFailDescription=" + fastPairStrings.getPairingFailDescription()
+                + ", assistantHalfSheetDescription="
+                + fastPairStrings.getAssistantHalfSheetDescription()
+                + ", assistantNotificationDescription="
+                + fastPairStrings.getAssistantNotificationDescription()
+                + ", fastPairTvConnectDeviceNoAccountDescription="
+                + fastPairStrings.getFastPairTvConnectDeviceNoAccountDescription()
+                + "]";
+    }
+
+    private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response) {
+        ObservedDeviceStrings strings = response.getStrings();
+        return FastPairStrings.newBuilder()
+                .setTapToPairWithAccount(strings.getInitialNotificationDescription())
+                .setTapToPairWithoutAccount(
+                        strings.getInitialNotificationDescriptionNoAccount())
+                .setInitialPairingDescription(strings.getInitialPairingDescription())
+                .setPairingFinishedCompanionAppInstalled(
+                        strings.getConnectSuccessCompanionAppInstalled())
+                .setPairingFinishedCompanionAppNotInstalled(
+                        strings.getConnectSuccessCompanionAppNotInstalled())
+                .setSubsequentPairingDescription(strings.getSubsequentPairingDescription())
+                .setRetroactivePairingDescription(strings.getRetroactivePairingDescription())
+                .setWaitAppLaunchDescription(strings.getWaitLaunchCompanionAppDescription())
+                .setPairingFailDescription(strings.getFailConnectGoToSettingsDescription())
+                .setAssistantHalfSheetDescription(strings.getAssistantSetupHalfSheet())
+                .setAssistantNotificationDescription(strings.getAssistantSetupNotification())
+                .setFastPairTvConnectDeviceNoAccountDescription(
+                        strings.getFastPairTvConnectDeviceNoAccountDescription())
+                .build();
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java
index dc131e7..e7277cd 100644
--- a/nearby/service/java/com/android/server/nearby/util/Environment.java
+++ b/nearby/service/java/com/android/server/nearby/util/Environment.java
@@ -29,7 +29,7 @@
     /**
      * NEARBY apex name.
      */
-    private static final String NEARBY_APEX_NAME = "com.android.nearby";
+    private static final String NEARBY_APEX_NAME = "com.android.tethering";
 
     /**
      * The path where the Nearby apex is mounted.
diff --git a/nearby/service/java/com/android/server/nearby/util/Hex.java b/nearby/service/java/com/android/server/nearby/util/Hex.java
index c827ec5..446204e 100644
--- a/nearby/service/java/com/android/server/nearby/util/Hex.java
+++ b/nearby/service/java/com/android/server/nearby/util/Hex.java
@@ -30,8 +30,8 @@
     public static String bytesToStringLowercase(byte[] bytes) {
         char[] hexChars = new char[bytes.length * 2];
         int j = 0;
-        for (int i = 0; i < bytes.length; i++) {
-            int v = bytes[i] & 0xFF;
+        for (byte aByte : bytes) {
+            int v = aByte & 0xFF;
             hexChars[j++] = HEX_LOWERCASE[v >>> 4];
             hexChars[j++] = HEX_LOWERCASE[v & 0x0F];
         }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
new file mode 100644
index 0000000..19a4a40
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.CredentialElement;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class CredentialElementTest {
+    private static final String KEY = "SECRETE_ID";
+    private static final byte[] VALUE = new byte[]{1, 2, 3, 4};
+
+    @Test
+    public void testBuilder() {
+        CredentialElement element = new CredentialElement(KEY, VALUE);
+
+        assertThat(element.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(element.getValue(), VALUE)).isTrue();
+    }
+
+    @Test
+    public void testWriteParcel() {
+        CredentialElement element = new CredentialElement(KEY, VALUE);
+
+        Parcel parcel = Parcel.obtain();
+        element.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CredentialElement elementFromParcel = element.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(elementFromParcel.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(elementFromParcel.getValue(), VALUE)).isTrue();
+    }
+
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
new file mode 100644
index 0000000..eb03a0d
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.DataElement;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class DataElementTest {
+
+    private static final int KEY = 1234;
+    private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
+
+    @Test
+    public void testBuilder() {
+        DataElement dataElement = new DataElement(KEY, VALUE);
+
+        assertThat(dataElement.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(dataElement.getValue(), VALUE)).isTrue();
+    }
+
+    @Test
+    public void testWriteParcel() {
+        DataElement dataElement = new DataElement(KEY, VALUE);
+
+        Parcel parcel = Parcel.obtain();
+        dataElement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        DataElement elementFromParcel = DataElement.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(elementFromParcel.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(elementFromParcel.getValue(), VALUE)).isTrue();
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
index 743a8e7..71fc330 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
@@ -150,6 +150,7 @@
     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;
@@ -707,6 +708,7 @@
         builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
         builder.setIntentUri(INTENT_URI);
         builder.setLocale(LOCALE);
+        builder.setName(NAME);
         builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
         builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
         builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
@@ -751,6 +753,7 @@
         parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
         parcel.intentUri = INTENT_URI;
         parcel.locale = LOCALE;
+        parcel.name = NAME;
         parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
         parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
         parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
@@ -890,6 +893,7 @@
         assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
 
         assertThat(metadataParcel.locale).isEqualTo(LOCALE);
+        assertThat(metadataParcel.name).isEqualTo(NAME);
 
         assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
                 OPEN_COMPANION_APP_DESCRIPTION);
@@ -949,6 +953,7 @@
         assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
         assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
         assertThat(metadata.getLocale()).isEqualTo(LOCALE);
+        assertThat(metadata.getName()).isEqualTo(NAME);
         assertThat(metadata.getOpenCompanionAppDescription())
                 .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
         assertThat(metadata.getRetroactivePairingDescription())
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 081626b..82e6615 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
@@ -21,32 +21,47 @@
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
 import android.os.Build;
+import android.os.Parcel;
 
 import androidx.annotation.RequiresApi;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class NearbyDeviceParcelableTest {
 
     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 String FAST_PAIR_MODEL_ID = "1234";
+    private static final int RSSI = -60;
+
+    private NearbyDeviceParcelable.Builder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder =  new NearbyDeviceParcelable.Builder()
+                .setName("testDevice")
+                .setMedium(NearbyDevice.Medium.BLE)
+                .setRssi(RSSI)
+                .setFastPairModelId(FAST_PAIR_MODEL_ID)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setData(SCAN_DATA);
+    }
+
 
     /** Verify toString returns expected string. */
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testToString() {
-        NearbyDeviceParcelable nearbyDeviceParcelable =  new NearbyDeviceParcelable.Builder()
-                .setName("testDevice")
-                .setMedium(NearbyDevice.Medium.BLE)
-                .setRssi(-60)
-                .setFastPairModelId(null)
-                .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                .setData(null)
-                .build();
+        NearbyDeviceParcelable nearbyDeviceParcelable =
+                mBuilder.setFastPairModelId(null).setData(null).build();
 
         assertThat(nearbyDeviceParcelable.toString()).isEqualTo(
                 "NearbyDeviceParcelable[name=testDevice, medium=BLE, rssi=-60, "
@@ -59,7 +74,7 @@
     public void test_defaultNullFields() {
         NearbyDeviceParcelable nearbyDeviceParcelable =  new NearbyDeviceParcelable.Builder()
                 .setMedium(NearbyDevice.Medium.BLE)
-                .setRssi(-60)
+                .setRssi(RSSI)
                 .build();
 
         assertThat(nearbyDeviceParcelable.getName()).isNull();
@@ -68,6 +83,56 @@
         assertThat(nearbyDeviceParcelable.getData()).isNull();
 
         assertThat(nearbyDeviceParcelable.getMedium()).isEqualTo(NearbyDevice.Medium.BLE);
-        assertThat(nearbyDeviceParcelable.getRssi()).isEqualTo(-60);
+        assertThat(nearbyDeviceParcelable.getRssi()).isEqualTo(RSSI);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteParcel() {
+        NearbyDeviceParcelable nearbyDeviceParcelable =  mBuilder.build();
+
+        Parcel parcel = Parcel.obtain();
+        nearbyDeviceParcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(actualNearbyDevice.getRssi()).isEqualTo(RSSI);
+        assertThat(actualNearbyDevice.getFastPairModelId()).isEqualTo(FAST_PAIR_MODEL_ID);
+        assertThat(actualNearbyDevice.getBluetoothAddress()).isEqualTo(BLUETOOTH_ADDRESS);
+        assertThat(Arrays.equals(actualNearbyDevice.getData(), SCAN_DATA)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteParcel_nullModelId() {
+        NearbyDeviceParcelable nearbyDeviceParcelable =
+                mBuilder.setFastPairModelId(null).build();
+
+        Parcel parcel = Parcel.obtain();
+        nearbyDeviceParcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(actualNearbyDevice.getFastPairModelId()).isNull();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteParcel_nullBluetoothAddress() {
+        NearbyDeviceParcelable nearbyDeviceParcelable =
+                mBuilder.setBluetoothAddress(null).build();
+
+        Parcel parcel = Parcel.obtain();
+        nearbyDeviceParcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(actualNearbyDevice.getBluetoothAddress()).isNull();
     }
 }
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 aad3fca..f37800a 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
@@ -49,11 +49,11 @@
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void test_getMedium_fromChild() {
         FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .setMedium(NearbyDevice.Medium.BLE)
+                .addMedium(NearbyDevice.Medium.BLE)
                 .setRssi(-60)
                 .build();
 
-        assertThat(fastPairDevice.getMedium()).isEqualTo(1);
+        assertThat(fastPairDevice.getMediums()).contains(1);
         assertThat(fastPairDevice.getRssi()).isEqualTo(-60);
     }
 }
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 07e8558..f32ef12 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -16,12 +16,18 @@
 
 package android.nearby.cts;
 
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.content.Context;
+import android.nearby.BroadcastCallback;
+import android.nearby.BroadcastRequest;
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyManager;
+import android.nearby.PresenceBroadcastRequest;
+import android.nearby.PrivateCredential;
 import android.nearby.ScanCallback;
 import android.nearby.ScanRequest;
 import android.os.Build;
@@ -35,6 +41,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.Collections;
 import java.util.concurrent.Executors;
 
 /**
@@ -44,9 +51,13 @@
 @RunWith(AndroidJUnit4.class)
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class NearbyManagerTest {
+    private static final byte[] SALT = new byte[] {1, 2};
+    private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
+    private static final int BLE_MEDIUM = 1;
 
-    @Mock Context mContext;
-    @Mock NearbyManager mNearbyManager;
+    @Mock private Context mContext;
+    @Mock private NearbyManager mNearbyManager;
 
     @Before
     public void setUp() {
@@ -79,4 +90,21 @@
         mNearbyManager.startScan(scanRequest, Executors.newSingleThreadExecutor(), scanCallback);
         mNearbyManager.stopScan(scanCallback);
     }
+
+    @Test
+    public void testStartStopBroadcast() {
+        PrivateCredential credential = new PrivateCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .build();
+        BroadcastRequest broadcastRequest =
+                new PresenceBroadcastRequest.Builder(Collections.singletonList(BLE_MEDIUM), SALT)
+                        .setCredential(credential)
+                        .build();
+
+        BroadcastCallback callback = status -> {
+        };
+        mNearbyManager.startBroadcast(broadcastRequest, Executors.newSingleThreadExecutor(),
+                callback);
+        mNearbyManager.stopBroadcast(callback);
+    }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
new file mode 100644
index 0000000..a37cc67
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.cts;
+
+import static android.nearby.BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE;
+import static android.nearby.BroadcastRequest.PRESENCE_VERSION_V0;
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.DataElement;
+import android.nearby.PresenceBroadcastRequest;
+import android.nearby.PrivateCredential;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests for {@link PresenceBroadcastRequest}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class PresenceBroadcastRequestTest {
+
+    private static final int VERSION = PRESENCE_VERSION_V0;
+    private static final int TX_POWER = 1;
+    private static final byte[] SALT = new byte[]{1, 2};
+    private static final int ACTION_ID = 123;
+    private static final int BLE_MEDIUM = 1;
+    private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
+    private static final byte[] METADATA_ENCRYPTION_KEY = new byte[]{1, 1, 3, 4, 5};
+    private static final int KEY = 1234;
+    private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
+
+    private PresenceBroadcastRequest.Builder mBuilder;
+
+    @Before
+    public void setUp() {
+        PrivateCredential credential = new PrivateCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .setMetadataEncryptionKey(METADATA_ENCRYPTION_KEY)
+                .build();
+        DataElement element = new DataElement(KEY, VALUE);
+        mBuilder = new PresenceBroadcastRequest.Builder(Collections.singletonList(BLE_MEDIUM), SALT)
+                .setTxPower(TX_POWER)
+                .setVersion(VERSION)
+                .setCredential(credential)
+                .addAction(ACTION_ID)
+                .addExtendedProperty(element);
+    }
+
+    @Test
+    public void testBuilder() {
+        PresenceBroadcastRequest broadcastRequest = mBuilder.build();
+
+        assertThat(broadcastRequest.getVersion()).isEqualTo(VERSION);
+        assertThat(Arrays.equals(broadcastRequest.getSalt(), SALT)).isTrue();
+        assertThat(broadcastRequest.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(broadcastRequest.getActions()).containsExactly(ACTION_ID);
+        assertThat(broadcastRequest.getExtendedProperties().get(0).getKey()).isEqualTo(
+                KEY);
+        assertThat(broadcastRequest.getMediums()).containsExactly(BLE_MEDIUM);
+        assertThat(broadcastRequest.getCredential().getIdentityType()).isEqualTo(
+                IDENTITY_TYPE_PRIVATE);
+        assertThat(broadcastRequest.getType()).isEqualTo(BROADCAST_TYPE_NEARBY_PRESENCE);
+    }
+
+    @Test
+    public void testWriteParcel() {
+        PresenceBroadcastRequest broadcastRequest = mBuilder.build();
+
+        Parcel parcel = Parcel.obtain();
+        broadcastRequest.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PresenceBroadcastRequest parcelRequest = PresenceBroadcastRequest.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(parcelRequest.getTxPower()).isEqualTo(TX_POWER);
+        assertThat(parcelRequest.getActions()).containsExactly(ACTION_ID);
+        assertThat(parcelRequest.getExtendedProperties().get(0).getKey()).isEqualTo(
+                KEY);
+        assertThat(parcelRequest.getMediums()).containsExactly(BLE_MEDIUM);
+        assertThat(parcelRequest.getCredential().getIdentityType()).isEqualTo(
+                IDENTITY_TYPE_PRIVATE);
+        assertThat(parcelRequest.getType()).isEqualTo(BROADCAST_TYPE_NEARBY_PRESENCE);
+
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
new file mode 100644
index 0000000..a1b282d
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.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 android.nearby.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.DataElement;
+import android.nearby.NearbyDevice;
+import android.nearby.PresenceDevice;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Test for {@link PresenceDevice}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class PresenceDeviceTest {
+    private static final int DEVICE_TYPE = PresenceDevice.DeviceType.PHONE;
+    private static final String DEVICE_ID = "123";
+    private static final String IMAGE_URL = "http://example.com/imageUrl";
+    private static final int RSSI = -40;
+    private static final int MEDIUM = NearbyDevice.Medium.BLE;
+    private static final String DEVICE_NAME = "testDevice";
+    private static final int KEY = 1234;
+    private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
+    private static final byte[] SALT = new byte[]{2, 3};
+    private static final byte[] SECRET_ID = new byte[]{11, 13};
+    private static final byte[] ENCRYPTED_IDENTITY = new byte[]{1, 3, 5, 61};
+    private static final long DISCOVERY_MILLIS = 100L;
+
+    @Test
+    public void testBuilder() {
+        PresenceDevice device = new PresenceDevice.Builder()
+                .setDeviceType(DEVICE_TYPE)
+                .setDeviceId(DEVICE_ID)
+                .setDeviceImageUrl(IMAGE_URL)
+                .addExtendedProperty(new DataElement(KEY, VALUE))
+                .setRssi(RSSI)
+                .addMedium(MEDIUM)
+                .setName(DEVICE_NAME)
+                .setDiscoveryTimestampMillis(DISCOVERY_MILLIS)
+                .setSalt(SALT)
+                .setSecretId(SECRET_ID)
+                .setEncryptedIdentity(ENCRYPTED_IDENTITY)
+                .build();
+
+        assertThat(device.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(device.getDeviceId()).isEqualTo(DEVICE_ID);
+        assertThat(device.getDeviceImageUrl()).isEqualTo(IMAGE_URL);
+        DataElement dataElement = device.getExtendedProperties().get(0);
+        assertThat(dataElement.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(dataElement.getValue(), VALUE)).isTrue();
+        assertThat(device.getRssi()).isEqualTo(RSSI);
+        assertThat(device.getMediums()).containsExactly(MEDIUM);
+        assertThat(device.getName()).isEqualTo(DEVICE_NAME);
+        assertThat(Arrays.equals(device.getSalt(), SALT)).isTrue();
+        assertThat(Arrays.equals(device.getSecretId(), SECRET_ID)).isTrue();
+        assertThat(Arrays.equals(device.getEncryptedIdentity(), ENCRYPTED_IDENTITY)).isTrue();
+        assertThat(device.getDiscoveryTimestampMillis()).isEqualTo(DISCOVERY_MILLIS);
+    }
+
+    @Test
+    public void testWriteParcel() {
+        PresenceDevice device = new PresenceDevice.Builder()
+                .setDeviceId(DEVICE_ID)
+                .addExtendedProperty(new DataElement(KEY, VALUE))
+                .setRssi(RSSI)
+                .addMedium(MEDIUM)
+                .setName(DEVICE_NAME)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        device.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PresenceDevice parcelDevice = PresenceDevice.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(parcelDevice.getDeviceId()).isEqualTo(DEVICE_ID);
+        assertThat(parcelDevice.getExtendedProperties().get(0).getKey()).isEqualTo(KEY);
+        assertThat(parcelDevice.getRssi()).isEqualTo(RSSI);
+        assertThat(parcelDevice.getMediums()).containsExactly(MEDIUM);
+        assertThat(parcelDevice.getName()).isEqualTo(DEVICE_NAME);
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
new file mode 100644
index 0000000..308be9e
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.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 android.nearby.cts;
+
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.DataElement;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanRequest;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link android.nearby.PresenceScanFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class PresenceScanFilterTest {
+
+    private static final int RSSI = -40;
+    private static final int ACTION = 123;
+    private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
+    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 int KEY = 1234;
+    private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
+
+
+    private PublicCredential mPublicCredential =
+            new PublicCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
+                    .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                    .setPublicKey(PUBLIC_KEY).setEncryptedMetadata(ENCRYPTED_METADATA)
+                    .setEncryptedMetadataKeyTag(METADATA_ENCRYPTION_KEY_TAG).build();
+    private PresenceScanFilter.Builder mBuilder = new PresenceScanFilter.Builder()
+            .setMaxPathLoss(RSSI)
+            .addCredential(mPublicCredential)
+            .addPresenceAction(ACTION)
+            .addExtendedProperty(new DataElement(KEY, VALUE));
+
+    @Test
+    public void testBuilder() {
+        PresenceScanFilter filter = mBuilder.build();
+
+        assertThat(filter.getMaxPathLoss()).isEqualTo(RSSI);
+        assertThat(filter.getCredentials().get(0).getIdentityType()).isEqualTo(
+                IDENTITY_TYPE_PRIVATE);
+        assertThat(filter.getPresenceActions()).containsExactly(ACTION);
+        assertThat(filter.getExtendedProperties().get(0).getKey()).isEqualTo(KEY);
+
+    }
+
+    @Test
+    public void testWriteParcel() {
+        PresenceScanFilter filter = mBuilder.build();
+
+        Parcel parcel = Parcel.obtain();
+        filter.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PresenceScanFilter parcelFilter = PresenceScanFilter.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(parcelFilter.getType()).isEqualTo(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE);
+        assertThat(parcelFilter.getMaxPathLoss()).isEqualTo(RSSI);
+        assertThat(parcelFilter.getPresenceActions()).containsExactly(ACTION);
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
new file mode 100644
index 0000000..5242999
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.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.cts;
+
+import static android.nearby.PresenceCredential.CREDENTIAL_TYPE_PRIVATE;
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.CredentialElement;
+import android.nearby.PrivateCredential;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link PrivateCredential}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class PrivateCredentialTest {
+    private static final String DEVICE_NAME = "myDevice";
+    private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
+    private static final String KEY = "SecreteId";
+    private static final byte[] VALUE = new byte[]{1, 2, 3, 4, 5};
+    private static final byte[] METADATA_ENCRYPTION_KEY = new byte[]{1, 1, 3, 4, 5};
+
+    private PrivateCredential.Builder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new PrivateCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .setDeviceName(DEVICE_NAME)
+                .setMetadataEncryptionKey(METADATA_ENCRYPTION_KEY)
+                .addCredentialElement(new CredentialElement(KEY, VALUE));
+    }
+
+    @Test
+    public void testBuilder() {
+        PrivateCredential credential = mBuilder.build();
+
+        assertThat(credential.getType()).isEqualTo(CREDENTIAL_TYPE_PRIVATE);
+        assertThat(credential.getIdentityType()).isEqualTo(IDENTITY_TYPE_PRIVATE);
+        assertThat(credential.getDeviceName()).isEqualTo(DEVICE_NAME);
+        assertThat(Arrays.equals(credential.getSecretId(), SECRETE_ID)).isTrue();
+        assertThat(Arrays.equals(credential.getAuthenticityKey(), AUTHENTICITY_KEY)).isTrue();
+        assertThat(Arrays.equals(credential.getMetadataEncryptionKey(),
+                METADATA_ENCRYPTION_KEY)).isTrue();
+        CredentialElement credentialElement = credential.getCredentialElements().get(0);
+        assertThat(credentialElement.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(credentialElement.getValue(), VALUE)).isTrue();
+    }
+
+    @Test
+    public void testWriteParcel() {
+        PrivateCredential credential = mBuilder.build();
+
+        Parcel parcel = Parcel.obtain();
+        credential.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PrivateCredential credentialFromParcel = PrivateCredential.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(credentialFromParcel.getType()).isEqualTo(CREDENTIAL_TYPE_PRIVATE);
+        assertThat(credentialFromParcel.getIdentityType()).isEqualTo(IDENTITY_TYPE_PRIVATE);
+        assertThat(Arrays.equals(credentialFromParcel.getSecretId(), SECRETE_ID)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getAuthenticityKey(),
+                AUTHENTICITY_KEY)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getMetadataEncryptionKey(),
+                METADATA_ENCRYPTION_KEY)).isTrue();
+        CredentialElement credentialElement = credentialFromParcel.getCredentialElements().get(0);
+        assertThat(credentialElement.getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(credentialElement.getValue(), VALUE)).isTrue();
+    }
+}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
new file mode 100644
index 0000000..f750951
--- /dev/null
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.cts;
+
+import static android.nearby.PresenceCredential.CREDENTIAL_TYPE_PUBLIC;
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.CredentialElement;
+import android.nearby.PresenceCredential;
+import android.nearby.PublicCredential;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link PresenceCredential}.
+ */
+@RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class PublicCredentialTest {
+
+    private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
+    private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
+    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 String KEY = "KEY";
+    private static final byte[] VALUE = new byte[]{1, 2, 3, 4, 5};
+
+    private PublicCredential.Builder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new PublicCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
+                .addCredentialElement(new CredentialElement(KEY, VALUE))
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .setPublicKey(PUBLIC_KEY).setEncryptedMetadata(ENCRYPTED_METADATA)
+                .setEncryptedMetadataKeyTag(METADATA_ENCRYPTION_KEY_TAG);
+    }
+
+    @Test
+    public void testBuilder() {
+        PublicCredential credential = mBuilder.build();
+
+        assertThat(credential.getType()).isEqualTo(CREDENTIAL_TYPE_PUBLIC);
+        assertThat(credential.getIdentityType()).isEqualTo(IDENTITY_TYPE_PRIVATE);
+        assertThat(credential.getCredentialElements().get(0).getKey()).isEqualTo(KEY);
+        assertThat(Arrays.equals(credential.getSecretId(), SECRETE_ID)).isTrue();
+        assertThat(Arrays.equals(credential.getAuthenticityKey(), AUTHENTICITY_KEY)).isTrue();
+        assertThat(Arrays.equals(credential.getPublicKey(), PUBLIC_KEY)).isTrue();
+        assertThat(Arrays.equals(credential.getEncryptedMetadata(), ENCRYPTED_METADATA)).isTrue();
+        assertThat(Arrays.equals(credential.getEncryptedMetadataKeyTag(),
+                METADATA_ENCRYPTION_KEY_TAG)).isTrue();
+    }
+
+    @Test
+    public void testWriteParcel() {
+        PublicCredential credential = mBuilder.build();
+
+        Parcel parcel = Parcel.obtain();
+        credential.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PublicCredential credentialFromParcel = PublicCredential.CREATOR.createFromParcel(
+                parcel);
+        parcel.recycle();
+
+        assertThat(credentialFromParcel.getType()).isEqualTo(CREDENTIAL_TYPE_PUBLIC);
+        assertThat(credentialFromParcel.getIdentityType()).isEqualTo(IDENTITY_TYPE_PRIVATE);
+        assertThat(Arrays.equals(credentialFromParcel.getSecretId(), SECRETE_ID)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getAuthenticityKey(),
+                AUTHENTICITY_KEY)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getPublicKey(), PUBLIC_KEY)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getEncryptedMetadata(),
+                ENCRYPTED_METADATA)).isTrue();
+        assertThat(Arrays.equals(credentialFromParcel.getEncryptedMetadataKeyTag(),
+                METADATA_ENCRYPTION_KEY_TAG)).isTrue();
+    }
+}
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 3bb348b..86e764c 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
@@ -16,6 +16,7 @@
 
 package android.nearby.cts;
 
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
 import static android.nearby.ScanRequest.SCAN_MODE_BALANCED;
 import static android.nearby.ScanRequest.SCAN_MODE_LOW_LATENCY;
 import static android.nearby.ScanRequest.SCAN_MODE_LOW_POWER;
@@ -27,6 +28,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
 import android.nearby.ScanRequest;
 import android.os.Build;
 import android.os.WorkSource;
@@ -45,6 +48,16 @@
     private static final int UID = 1001;
     private static final String APP_NAME = "android.nearby.tests";
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testScanType() {
+        ScanRequest request = new ScanRequest.Builder()
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                .build();
+
+        assertThat(request.getScanType()).isEqualTo(SCAN_TYPE_NEARBY_PRESENCE);
+    }
+
     // Valid scan type must be set to one of ScanRequest#SCAN_TYPE_
     @Test(expected = IllegalStateException.class)
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
@@ -62,7 +75,7 @@
         assertThat(request.getScanMode()).isEqualTo(SCAN_MODE_LOW_POWER);
     }
 
-    /** Verify setting work source with null value in the scan request is allowed*/
+    /** Verify setting work source with null value in the scan request is allowed */
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testSetWorkSource_nullValue() {
@@ -151,6 +164,36 @@
         assertThat(ScanRequest.scanModeToString(-2)).isEqualTo("SCAN_MODE_INVALID");
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testScanFilter() {
+        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)
+                .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                .setEncryptedMetadata(encryptedMetadata)
+                .setPublicKey(publicKey)
+                .setEncryptedMetadataKeyTag(metadataEncryptionKeyTag).build();
+
+        final int rssi = -40;
+        final int action = 123;
+        PresenceScanFilter filter = new PresenceScanFilter.Builder()
+                .addCredential(credential)
+                .setMaxPathLoss(rssi)
+                .addPresenceAction(action)
+                .build();
+
+        ScanRequest request = new ScanRequest.Builder().setScanType(
+                SCAN_TYPE_FAST_PAIR).addScanFilter(filter).build();
+
+        assertThat(request.getScanFilters()).isNotEmpty();
+        assertThat(request.getScanFilters().get(0).getMaxPathLoss()).isEqualTo(rssi);
+    }
+
     private static WorkSource getWorkSource() {
         return new WorkSource(UID, APP_NAME);
     }
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 93ab20a..9b5b14c 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -33,18 +33,14 @@
     compile_multilib: "both",
 
     static_libs: [
-        "Robolectric_all-target",
         "androidx.test.ext.junit",
         "androidx.test.rules",
-        "compatibility-device-util-axt",
         "framework-nearby-static",
         "guava",
         "junit",
         "libprotobuf-java-lite",
         "mockito-target",
         "platform-test-annotations",
-        "robolectric_android-all-stub",
-        "service-appsearch",
         "service-nearby",
         "truth-prebuilt",
     ],
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
new file mode 100644
index 0000000..b40a5a5
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.ImmutableSet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link Preferences}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferencesTest {
+
+    private static final int FIRST_INT = 1505;
+    private static final int SECOND_INT = 1506;
+    private static final boolean FIRST_BOOL = true;
+    private static final boolean SECOND_BOOL = false;
+    private static final short FIRST_SHORT = 32;
+    private static final short SECOND_SHORT = 73;
+    private static final long FIRST_LONG = 9838L;
+    private static final long SECOND_LONG = 93935L;
+    private static final String FIRST_STRING = "FIRST_STRING";
+    private static final String SECOND_STRING = "SECOND_STRING";
+    private static final byte[] FIRST_BYTES = new byte[] {7, 9};
+    private static final byte[] SECOND_BYTES = new byte[] {2};
+    private static final ImmutableSet<Integer> FIRST_INT_SETS = ImmutableSet.of(6, 8);
+    private static final ImmutableSet<Integer> SECOND_INT_SETS = ImmutableSet.of(6, 8);
+    private static final Preferences.ExtraLoggingInformation FIRST_EXTRA_LOGGING_INFO =
+            Preferences.ExtraLoggingInformation.builder().setModelId("000006").build();
+    private static final Preferences.ExtraLoggingInformation SECOND_EXTRA_LOGGING_INFO =
+            Preferences.ExtraLoggingInformation.builder().setModelId("000007").build();
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattOperationTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setGattOperationTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getGattOperationTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getGattOperationTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattOperationTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getGattOperationTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectionTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectionTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getGattConnectionTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getGattConnectionTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectionTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getGattConnectionTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBluetoothToggleTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setBluetoothToggleTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getBluetoothToggleTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getBluetoothToggleTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBluetoothToggleTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getBluetoothToggleTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBluetoothToggleSleepSeconds() {
+        Preferences prefs =
+                Preferences.builder().setBluetoothToggleSleepSeconds(FIRST_INT).build();
+        assertThat(prefs.getBluetoothToggleSleepSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getBluetoothToggleSleepSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBluetoothToggleSleepSeconds(SECOND_INT).build();
+        assertThat(prefs2.getBluetoothToggleSleepSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testClassicDiscoveryTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setClassicDiscoveryTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getClassicDiscoveryTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getClassicDiscoveryTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setClassicDiscoveryTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getClassicDiscoveryTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumDiscoverAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumDiscoverAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumDiscoverAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumDiscoverAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumDiscoverAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumDiscoverAttempts()).isEqualTo(SECOND_INT);
+    }
+
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDiscoveryRetrySleepSeconds() {
+        Preferences prefs =
+                Preferences.builder().setDiscoveryRetrySleepSeconds(FIRST_INT).build();
+        assertThat(prefs.getDiscoveryRetrySleepSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getDiscoveryRetrySleepSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setDiscoveryRetrySleepSeconds(SECOND_INT).build();
+        assertThat(prefs2.getDiscoveryRetrySleepSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSdpTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setSdpTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getSdpTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getSdpTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setSdpTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getSdpTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumSdpAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumSdpAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumSdpAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumSdpAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumSdpAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumSdpAttempts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumCreateBondAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumCreateBondAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumCreateBondAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumCreateBondAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumCreateBondAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumCreateBondAttempts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumConnectAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumConnectAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumConnectAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumConnectAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumConnectAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumConnectAttempts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumWriteAccountKeyAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumWriteAccountKeyAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumWriteAccountKeyAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumWriteAccountKeyAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumWriteAccountKeyAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumWriteAccountKeyAttempts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBluetoothStatePollingMillis() {
+        Preferences prefs =
+                Preferences.builder().setBluetoothStatePollingMillis(FIRST_INT).build();
+        assertThat(prefs.getBluetoothStatePollingMillis()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getBluetoothStatePollingMillis())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBluetoothStatePollingMillis(SECOND_INT).build();
+        assertThat(prefs2.getBluetoothStatePollingMillis()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumAttempts() {
+        Preferences prefs =
+                Preferences.builder().setNumAttempts(FIRST_INT).build();
+        assertThat(prefs.getNumAttempts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumAttempts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumAttempts(SECOND_INT).build();
+        assertThat(prefs2.getNumAttempts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRemoveBondTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setRemoveBondTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getRemoveBondTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getRemoveBondTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setRemoveBondTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getRemoveBondTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRemoveBondSleepMillis() {
+        Preferences prefs =
+                Preferences.builder().setRemoveBondSleepMillis(FIRST_INT).build();
+        assertThat(prefs.getRemoveBondSleepMillis()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getRemoveBondSleepMillis())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setRemoveBondSleepMillis(SECOND_INT).build();
+        assertThat(prefs2.getRemoveBondSleepMillis()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreateBondTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setCreateBondTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getCreateBondTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getCreateBondTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setCreateBondTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getCreateBondTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHidCreateBondTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setHidCreateBondTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getHidCreateBondTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getHidCreateBondTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setHidCreateBondTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getHidCreateBondTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testProxyTimeoutSeconds() {
+        Preferences prefs =
+                Preferences.builder().setProxyTimeoutSeconds(FIRST_INT).build();
+        assertThat(prefs.getProxyTimeoutSeconds()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getProxyTimeoutSeconds())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setProxyTimeoutSeconds(SECOND_INT).build();
+        assertThat(prefs2.getProxyTimeoutSeconds()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWriteAccountKeySleepMillis() {
+        Preferences prefs =
+                Preferences.builder().setWriteAccountKeySleepMillis(FIRST_INT).build();
+        assertThat(prefs.getWriteAccountKeySleepMillis()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getWriteAccountKeySleepMillis())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setWriteAccountKeySleepMillis(SECOND_INT).build();
+        assertThat(prefs2.getWriteAccountKeySleepMillis()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testPairFailureCounts() {
+        Preferences prefs =
+                Preferences.builder().setPairFailureCounts(FIRST_INT).build();
+        assertThat(prefs.getPairFailureCounts()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getPairFailureCounts())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setPairFailureCounts(SECOND_INT).build();
+        assertThat(prefs2.getPairFailureCounts()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCreateBondTransportType() {
+        Preferences prefs =
+                Preferences.builder().setCreateBondTransportType(FIRST_INT).build();
+        assertThat(prefs.getCreateBondTransportType()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getCreateBondTransportType())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setCreateBondTransportType(SECOND_INT).build();
+        assertThat(prefs2.getCreateBondTransportType()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectRetryTimeoutMillis() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectRetryTimeoutMillis(FIRST_INT).build();
+        assertThat(prefs.getGattConnectRetryTimeoutMillis()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getGattConnectRetryTimeoutMillis())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectRetryTimeoutMillis(SECOND_INT).build();
+        assertThat(prefs2.getGattConnectRetryTimeoutMillis()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testNumSdpAttemptsAfterBonded() {
+        Preferences prefs =
+                Preferences.builder().setNumSdpAttemptsAfterBonded(FIRST_INT).build();
+        assertThat(prefs.getNumSdpAttemptsAfterBonded()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getNumSdpAttemptsAfterBonded())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setNumSdpAttemptsAfterBonded(SECOND_INT).build();
+        assertThat(prefs2.getNumSdpAttemptsAfterBonded()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSameModelIdPairedDeviceCount() {
+        Preferences prefs =
+                Preferences.builder().setSameModelIdPairedDeviceCount(FIRST_INT).build();
+        assertThat(prefs.getSameModelIdPairedDeviceCount()).isEqualTo(FIRST_INT);
+        assertThat(prefs.toBuilder().build().getSameModelIdPairedDeviceCount())
+                .isEqualTo(FIRST_INT);
+
+        Preferences prefs2 =
+                Preferences.builder().setSameModelIdPairedDeviceCount(SECOND_INT).build();
+        assertThat(prefs2.getSameModelIdPairedDeviceCount()).isEqualTo(SECOND_INT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIgnoreDiscoveryError() {
+        Preferences prefs =
+                Preferences.builder().setIgnoreDiscoveryError(FIRST_BOOL).build();
+        assertThat(prefs.getIgnoreDiscoveryError()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getIgnoreDiscoveryError())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setIgnoreDiscoveryError(SECOND_BOOL).build();
+        assertThat(prefs2.getIgnoreDiscoveryError()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testToggleBluetoothOnFailure() {
+        Preferences prefs =
+                Preferences.builder().setToggleBluetoothOnFailure(FIRST_BOOL).build();
+        assertThat(prefs.getToggleBluetoothOnFailure()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getToggleBluetoothOnFailure())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setToggleBluetoothOnFailure(SECOND_BOOL).build();
+        assertThat(prefs2.getToggleBluetoothOnFailure()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBluetoothStateUsesPolling() {
+        Preferences prefs =
+                Preferences.builder().setBluetoothStateUsesPolling(FIRST_BOOL).build();
+        assertThat(prefs.getBluetoothStateUsesPolling()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getBluetoothStateUsesPolling())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setBluetoothStateUsesPolling(SECOND_BOOL).build();
+        assertThat(prefs2.getBluetoothStateUsesPolling()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnableBrEdrHandover() {
+        Preferences prefs =
+                Preferences.builder().setEnableBrEdrHandover(FIRST_BOOL).build();
+        assertThat(prefs.getEnableBrEdrHandover()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnableBrEdrHandover())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnableBrEdrHandover(SECOND_BOOL).build();
+        assertThat(prefs2.getEnableBrEdrHandover()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWaitForUuidsAfterBonding() {
+        Preferences prefs =
+                Preferences.builder().setWaitForUuidsAfterBonding(FIRST_BOOL).build();
+        assertThat(prefs.getWaitForUuidsAfterBonding()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getWaitForUuidsAfterBonding())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setWaitForUuidsAfterBonding(SECOND_BOOL).build();
+        assertThat(prefs2.getWaitForUuidsAfterBonding()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testReceiveUuidsAndBondedEventBeforeClose() {
+        Preferences prefs =
+                Preferences.builder().setReceiveUuidsAndBondedEventBeforeClose(FIRST_BOOL).build();
+        assertThat(prefs.getReceiveUuidsAndBondedEventBeforeClose()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getReceiveUuidsAndBondedEventBeforeClose())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setReceiveUuidsAndBondedEventBeforeClose(SECOND_BOOL).build();
+        assertThat(prefs2.getReceiveUuidsAndBondedEventBeforeClose()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRejectPhonebookAccess() {
+        Preferences prefs =
+                Preferences.builder().setRejectPhonebookAccess(FIRST_BOOL).build();
+        assertThat(prefs.getRejectPhonebookAccess()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getRejectPhonebookAccess())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setRejectPhonebookAccess(SECOND_BOOL).build();
+        assertThat(prefs2.getRejectPhonebookAccess()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRejectMessageAccess() {
+        Preferences prefs =
+                Preferences.builder().setRejectMessageAccess(FIRST_BOOL).build();
+        assertThat(prefs.getRejectMessageAccess()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getRejectMessageAccess())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setRejectMessageAccess(SECOND_BOOL).build();
+        assertThat(prefs2.getRejectMessageAccess()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRejectSimAccess() {
+        Preferences prefs =
+                Preferences.builder().setRejectSimAccess(FIRST_BOOL).build();
+        assertThat(prefs.getRejectSimAccess()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getRejectSimAccess())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setRejectSimAccess(SECOND_BOOL).build();
+        assertThat(prefs2.getRejectSimAccess()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSkipDisconnectingGattBeforeWritingAccountKey() {
+        Preferences prefs =
+                Preferences.builder().setSkipDisconnectingGattBeforeWritingAccountKey(FIRST_BOOL)
+                        .build();
+        assertThat(prefs.getSkipDisconnectingGattBeforeWritingAccountKey()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getSkipDisconnectingGattBeforeWritingAccountKey())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setSkipDisconnectingGattBeforeWritingAccountKey(SECOND_BOOL)
+                        .build();
+        assertThat(prefs2.getSkipDisconnectingGattBeforeWritingAccountKey()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testMoreEventLogForQuality() {
+        Preferences prefs =
+                Preferences.builder().setMoreEventLogForQuality(FIRST_BOOL).build();
+        assertThat(prefs.getMoreEventLogForQuality()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getMoreEventLogForQuality())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setMoreEventLogForQuality(SECOND_BOOL).build();
+        assertThat(prefs2.getMoreEventLogForQuality()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRetryGattConnectionAndSecretHandshake() {
+        Preferences prefs =
+                Preferences.builder().setRetryGattConnectionAndSecretHandshake(FIRST_BOOL).build();
+        assertThat(prefs.getRetryGattConnectionAndSecretHandshake()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getRetryGattConnectionAndSecretHandshake())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setRetryGattConnectionAndSecretHandshake(SECOND_BOOL).build();
+        assertThat(prefs2.getRetryGattConnectionAndSecretHandshake()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testRetrySecretHandshakeTimeout() {
+        Preferences prefs =
+                Preferences.builder().setRetrySecretHandshakeTimeout(FIRST_BOOL).build();
+        assertThat(prefs.getRetrySecretHandshakeTimeout()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getRetrySecretHandshakeTimeout())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setRetrySecretHandshakeTimeout(SECOND_BOOL).build();
+        assertThat(prefs2.getRetrySecretHandshakeTimeout()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLogUserManualRetry() {
+        Preferences prefs =
+                Preferences.builder().setLogUserManualRetry(FIRST_BOOL).build();
+        assertThat(prefs.getLogUserManualRetry()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getLogUserManualRetry())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setLogUserManualRetry(SECOND_BOOL).build();
+        assertThat(prefs2.getLogUserManualRetry()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsDeviceFinishCheckAddressFromCache() {
+        Preferences prefs =
+                Preferences.builder().setIsDeviceFinishCheckAddressFromCache(FIRST_BOOL).build();
+        assertThat(prefs.getIsDeviceFinishCheckAddressFromCache()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getIsDeviceFinishCheckAddressFromCache())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setIsDeviceFinishCheckAddressFromCache(SECOND_BOOL).build();
+        assertThat(prefs2.getIsDeviceFinishCheckAddressFromCache()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testLogPairWithCachedModelId() {
+        Preferences prefs =
+                Preferences.builder().setLogPairWithCachedModelId(FIRST_BOOL).build();
+        assertThat(prefs.getLogPairWithCachedModelId()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getLogPairWithCachedModelId())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setLogPairWithCachedModelId(SECOND_BOOL).build();
+        assertThat(prefs2.getLogPairWithCachedModelId()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testDirectConnectProfileIfModelIdInCache() {
+        Preferences prefs =
+                Preferences.builder().setDirectConnectProfileIfModelIdInCache(FIRST_BOOL).build();
+        assertThat(prefs.getDirectConnectProfileIfModelIdInCache()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getDirectConnectProfileIfModelIdInCache())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setDirectConnectProfileIfModelIdInCache(SECOND_BOOL).build();
+        assertThat(prefs2.getDirectConnectProfileIfModelIdInCache()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAcceptPasskey() {
+        Preferences prefs =
+                Preferences.builder().setAcceptPasskey(FIRST_BOOL).build();
+        assertThat(prefs.getAcceptPasskey()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getAcceptPasskey())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setAcceptPasskey(SECOND_BOOL).build();
+        assertThat(prefs2.getAcceptPasskey()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testProviderInitiatesBondingIfSupported() {
+        Preferences prefs =
+                Preferences.builder().setProviderInitiatesBondingIfSupported(FIRST_BOOL).build();
+        assertThat(prefs.getProviderInitiatesBondingIfSupported()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getProviderInitiatesBondingIfSupported())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setProviderInitiatesBondingIfSupported(SECOND_BOOL).build();
+        assertThat(prefs2.getProviderInitiatesBondingIfSupported()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAttemptDirectConnectionWhenPreviouslyBonded() {
+        Preferences prefs =
+                Preferences.builder()
+                        .setAttemptDirectConnectionWhenPreviouslyBonded(FIRST_BOOL).build();
+        assertThat(prefs.getAttemptDirectConnectionWhenPreviouslyBonded()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getAttemptDirectConnectionWhenPreviouslyBonded())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder()
+                        .setAttemptDirectConnectionWhenPreviouslyBonded(SECOND_BOOL).build();
+        assertThat(prefs2.getAttemptDirectConnectionWhenPreviouslyBonded()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAutomaticallyReconnectGattWhenNeeded() {
+        Preferences prefs =
+                Preferences.builder().setAutomaticallyReconnectGattWhenNeeded(FIRST_BOOL).build();
+        assertThat(prefs.getAutomaticallyReconnectGattWhenNeeded()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getAutomaticallyReconnectGattWhenNeeded())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setAutomaticallyReconnectGattWhenNeeded(SECOND_BOOL).build();
+        assertThat(prefs2.getAutomaticallyReconnectGattWhenNeeded()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSkipConnectingProfiles() {
+        Preferences prefs =
+                Preferences.builder().setSkipConnectingProfiles(FIRST_BOOL).build();
+        assertThat(prefs.getSkipConnectingProfiles()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getSkipConnectingProfiles())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setSkipConnectingProfiles(SECOND_BOOL).build();
+        assertThat(prefs2.getSkipConnectingProfiles()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIgnoreUuidTimeoutAfterBonded() {
+        Preferences prefs =
+                Preferences.builder().setIgnoreUuidTimeoutAfterBonded(FIRST_BOOL).build();
+        assertThat(prefs.getIgnoreUuidTimeoutAfterBonded()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getIgnoreUuidTimeoutAfterBonded())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setIgnoreUuidTimeoutAfterBonded(SECOND_BOOL).build();
+        assertThat(prefs2.getIgnoreUuidTimeoutAfterBonded()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSpecifyCreateBondTransportType() {
+        Preferences prefs =
+                Preferences.builder().setSpecifyCreateBondTransportType(FIRST_BOOL).build();
+        assertThat(prefs.getSpecifyCreateBondTransportType()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getSpecifyCreateBondTransportType())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setSpecifyCreateBondTransportType(SECOND_BOOL).build();
+        assertThat(prefs2.getSpecifyCreateBondTransportType()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIncreaseIntentFilterPriority() {
+        Preferences prefs =
+                Preferences.builder().setIncreaseIntentFilterPriority(FIRST_BOOL).build();
+        assertThat(prefs.getIncreaseIntentFilterPriority()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getIncreaseIntentFilterPriority())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setIncreaseIntentFilterPriority(SECOND_BOOL).build();
+        assertThat(prefs2.getIncreaseIntentFilterPriority()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEvaluatePerformance() {
+        Preferences prefs =
+                Preferences.builder().setEvaluatePerformance(FIRST_BOOL).build();
+        assertThat(prefs.getEvaluatePerformance()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEvaluatePerformance())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEvaluatePerformance(SECOND_BOOL).build();
+        assertThat(prefs2.getEvaluatePerformance()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnableNamingCharacteristic() {
+        Preferences prefs =
+                Preferences.builder().setEnableNamingCharacteristic(FIRST_BOOL).build();
+        assertThat(prefs.getEnableNamingCharacteristic()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnableNamingCharacteristic())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnableNamingCharacteristic(SECOND_BOOL).build();
+        assertThat(prefs2.getEnableNamingCharacteristic()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnableFirmwareVersionCharacteristic() {
+        Preferences prefs =
+                Preferences.builder().setEnableFirmwareVersionCharacteristic(FIRST_BOOL).build();
+        assertThat(prefs.getEnableFirmwareVersionCharacteristic()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnableFirmwareVersionCharacteristic())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnableFirmwareVersionCharacteristic(SECOND_BOOL).build();
+        assertThat(prefs2.getEnableFirmwareVersionCharacteristic()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testKeepSameAccountKeyWrite() {
+        Preferences prefs =
+                Preferences.builder().setKeepSameAccountKeyWrite(FIRST_BOOL).build();
+        assertThat(prefs.getKeepSameAccountKeyWrite()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getKeepSameAccountKeyWrite())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setKeepSameAccountKeyWrite(SECOND_BOOL).build();
+        assertThat(prefs2.getKeepSameAccountKeyWrite()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testIsRetroactivePairing() {
+        Preferences prefs =
+                Preferences.builder().setIsRetroactivePairing(FIRST_BOOL).build();
+        assertThat(prefs.getIsRetroactivePairing()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getIsRetroactivePairing())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setIsRetroactivePairing(SECOND_BOOL).build();
+        assertThat(prefs2.getIsRetroactivePairing()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSupportHidDevice() {
+        Preferences prefs =
+                Preferences.builder().setSupportHidDevice(FIRST_BOOL).build();
+        assertThat(prefs.getSupportHidDevice()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getSupportHidDevice())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setSupportHidDevice(SECOND_BOOL).build();
+        assertThat(prefs2.getSupportHidDevice()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnablePairingWhileDirectlyConnecting() {
+        Preferences prefs =
+                Preferences.builder().setEnablePairingWhileDirectlyConnecting(FIRST_BOOL).build();
+        assertThat(prefs.getEnablePairingWhileDirectlyConnecting()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnablePairingWhileDirectlyConnecting())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnablePairingWhileDirectlyConnecting(SECOND_BOOL).build();
+        assertThat(prefs2.getEnablePairingWhileDirectlyConnecting()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAcceptConsentForFastPairOne() {
+        Preferences prefs =
+                Preferences.builder().setAcceptConsentForFastPairOne(FIRST_BOOL).build();
+        assertThat(prefs.getAcceptConsentForFastPairOne()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getAcceptConsentForFastPairOne())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setAcceptConsentForFastPairOne(SECOND_BOOL).build();
+        assertThat(prefs2.getAcceptConsentForFastPairOne()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnable128BitCustomGattCharacteristicsId() {
+        Preferences prefs =
+                Preferences.builder().setEnable128BitCustomGattCharacteristicsId(FIRST_BOOL)
+                        .build();
+        assertThat(prefs.getEnable128BitCustomGattCharacteristicsId()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnable128BitCustomGattCharacteristicsId())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnable128BitCustomGattCharacteristicsId(SECOND_BOOL)
+                        .build();
+        assertThat(prefs2.getEnable128BitCustomGattCharacteristicsId()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnableSendExceptionStepToValidator() {
+        Preferences prefs =
+                Preferences.builder().setEnableSendExceptionStepToValidator(FIRST_BOOL).build();
+        assertThat(prefs.getEnableSendExceptionStepToValidator()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnableSendExceptionStepToValidator())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnableSendExceptionStepToValidator(SECOND_BOOL).build();
+        assertThat(prefs2.getEnableSendExceptionStepToValidator()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnableAdditionalDataTypeWhenActionOverBle() {
+        Preferences prefs =
+                Preferences.builder().setEnableAdditionalDataTypeWhenActionOverBle(FIRST_BOOL)
+                        .build();
+        assertThat(prefs.getEnableAdditionalDataTypeWhenActionOverBle()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnableAdditionalDataTypeWhenActionOverBle())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnableAdditionalDataTypeWhenActionOverBle(SECOND_BOOL)
+                        .build();
+        assertThat(prefs2.getEnableAdditionalDataTypeWhenActionOverBle()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCheckBondStateWhenSkipConnectingProfiles() {
+        Preferences prefs =
+                Preferences.builder().setCheckBondStateWhenSkipConnectingProfiles(FIRST_BOOL)
+                        .build();
+        assertThat(prefs.getCheckBondStateWhenSkipConnectingProfiles()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getCheckBondStateWhenSkipConnectingProfiles())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setCheckBondStateWhenSkipConnectingProfiles(SECOND_BOOL)
+                        .build();
+        assertThat(prefs2.getCheckBondStateWhenSkipConnectingProfiles()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHandlePasskeyConfirmationByUi() {
+        Preferences prefs =
+                Preferences.builder().setHandlePasskeyConfirmationByUi(FIRST_BOOL).build();
+        assertThat(prefs.getHandlePasskeyConfirmationByUi()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getHandlePasskeyConfirmationByUi())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setHandlePasskeyConfirmationByUi(SECOND_BOOL).build();
+        assertThat(prefs2.getHandlePasskeyConfirmationByUi()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEnablePairFlowShowUiWithoutProfileConnection() {
+        Preferences prefs =
+                Preferences.builder().setEnablePairFlowShowUiWithoutProfileConnection(FIRST_BOOL)
+                        .build();
+        assertThat(prefs.getEnablePairFlowShowUiWithoutProfileConnection()).isEqualTo(FIRST_BOOL);
+        assertThat(prefs.toBuilder().build().getEnablePairFlowShowUiWithoutProfileConnection())
+                .isEqualTo(FIRST_BOOL);
+
+        Preferences prefs2 =
+                Preferences.builder().setEnablePairFlowShowUiWithoutProfileConnection(SECOND_BOOL)
+                        .build();
+        assertThat(prefs2.getEnablePairFlowShowUiWithoutProfileConnection()).isEqualTo(SECOND_BOOL);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBrHandoverDataCharacteristicId() {
+        Preferences prefs =
+                Preferences.builder().setBrHandoverDataCharacteristicId(FIRST_SHORT).build();
+        assertThat(prefs.getBrHandoverDataCharacteristicId()).isEqualTo(FIRST_SHORT);
+        assertThat(prefs.toBuilder().build().getBrHandoverDataCharacteristicId())
+                .isEqualTo(FIRST_SHORT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBrHandoverDataCharacteristicId(SECOND_SHORT).build();
+        assertThat(prefs2.getBrHandoverDataCharacteristicId()).isEqualTo(SECOND_SHORT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBluetoothSigDataCharacteristicId() {
+        Preferences prefs =
+                Preferences.builder().setBluetoothSigDataCharacteristicId(FIRST_SHORT).build();
+        assertThat(prefs.getBluetoothSigDataCharacteristicId()).isEqualTo(FIRST_SHORT);
+        assertThat(prefs.toBuilder().build().getBluetoothSigDataCharacteristicId())
+                .isEqualTo(FIRST_SHORT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBluetoothSigDataCharacteristicId(SECOND_SHORT).build();
+        assertThat(prefs2.getBluetoothSigDataCharacteristicId()).isEqualTo(SECOND_SHORT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testFirmwareVersionCharacteristicId() {
+        Preferences prefs =
+                Preferences.builder().setFirmwareVersionCharacteristicId(FIRST_SHORT).build();
+        assertThat(prefs.getFirmwareVersionCharacteristicId()).isEqualTo(FIRST_SHORT);
+        assertThat(prefs.toBuilder().build().getFirmwareVersionCharacteristicId())
+                .isEqualTo(FIRST_SHORT);
+
+        Preferences prefs2 =
+                Preferences.builder().setFirmwareVersionCharacteristicId(SECOND_SHORT).build();
+        assertThat(prefs2.getFirmwareVersionCharacteristicId()).isEqualTo(SECOND_SHORT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testBrTransportBlockDataDescriptorId() {
+        Preferences prefs =
+                Preferences.builder().setBrTransportBlockDataDescriptorId(FIRST_SHORT).build();
+        assertThat(prefs.getBrTransportBlockDataDescriptorId()).isEqualTo(FIRST_SHORT);
+        assertThat(prefs.toBuilder().build().getBrTransportBlockDataDescriptorId())
+                .isEqualTo(FIRST_SHORT);
+
+        Preferences prefs2 =
+                Preferences.builder().setBrTransportBlockDataDescriptorId(SECOND_SHORT).build();
+        assertThat(prefs2.getBrTransportBlockDataDescriptorId()).isEqualTo(SECOND_SHORT);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectShortTimeoutMs() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectShortTimeoutMs(FIRST_LONG).build();
+        assertThat(prefs.getGattConnectShortTimeoutMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getGattConnectShortTimeoutMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectShortTimeoutMs(SECOND_LONG).build();
+        assertThat(prefs2.getGattConnectShortTimeoutMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectLongTimeoutMs() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectLongTimeoutMs(FIRST_LONG).build();
+        assertThat(prefs.getGattConnectLongTimeoutMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getGattConnectLongTimeoutMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectLongTimeoutMs(SECOND_LONG).build();
+        assertThat(prefs2.getGattConnectLongTimeoutMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectShortTimeoutRetryMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectShortTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
+                        .build();
+        assertThat(prefs.getGattConnectShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getGattConnectShortTimeoutRetryMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectShortTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
+                        .build();
+        assertThat(prefs2.getGattConnectShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testAddressRotateRetryMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder().setAddressRotateRetryMaxSpentTimeMs(FIRST_LONG).build();
+        assertThat(prefs.getAddressRotateRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getAddressRotateRetryMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setAddressRotateRetryMaxSpentTimeMs(SECOND_LONG).build();
+        assertThat(prefs2.getAddressRotateRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testPairingRetryDelayMs() {
+        Preferences prefs =
+                Preferences.builder().setPairingRetryDelayMs(FIRST_LONG).build();
+        assertThat(prefs.getPairingRetryDelayMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getPairingRetryDelayMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setPairingRetryDelayMs(SECOND_LONG).build();
+        assertThat(prefs2.getPairingRetryDelayMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeShortTimeoutMs() {
+        Preferences prefs =
+                Preferences.builder().setSecretHandshakeShortTimeoutMs(FIRST_LONG).build();
+        assertThat(prefs.getSecretHandshakeShortTimeoutMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeShortTimeoutMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeShortTimeoutMs(SECOND_LONG).build();
+        assertThat(prefs2.getSecretHandshakeShortTimeoutMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeLongTimeoutMs() {
+        Preferences prefs =
+                Preferences.builder().setSecretHandshakeLongTimeoutMs(FIRST_LONG).build();
+        assertThat(prefs.getSecretHandshakeLongTimeoutMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeLongTimeoutMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeLongTimeoutMs(SECOND_LONG).build();
+        assertThat(prefs2.getSecretHandshakeLongTimeoutMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder().setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
+                        .build();
+        assertThat(prefs.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
+                        .build();
+        assertThat(prefs2.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs())
+                .isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeLongTimeoutRetryMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder().setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
+                        .build();
+        assertThat(prefs.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
+                        .build();
+        assertThat(prefs2.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs())
+                .isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeRetryAttempts() {
+        Preferences prefs =
+                Preferences.builder().setSecretHandshakeRetryAttempts(FIRST_LONG).build();
+        assertThat(prefs.getSecretHandshakeRetryAttempts()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeRetryAttempts())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeRetryAttempts(SECOND_LONG).build();
+        assertThat(prefs2.getSecretHandshakeRetryAttempts()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSecretHandshakeRetryGattConnectionMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder()
+                        .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(FIRST_LONG).build();
+        assertThat(prefs.getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(
+                        SECOND_LONG).build();
+        assertThat(prefs2.getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
+                .isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSignalLostRetryMaxSpentTimeMs() {
+        Preferences prefs =
+                Preferences.builder().setSignalLostRetryMaxSpentTimeMs(FIRST_LONG).build();
+        assertThat(prefs.getSignalLostRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
+        assertThat(prefs.toBuilder().build().getSignalLostRetryMaxSpentTimeMs())
+                .isEqualTo(FIRST_LONG);
+
+        Preferences prefs2 =
+                Preferences.builder().setSignalLostRetryMaxSpentTimeMs(SECOND_LONG).build();
+        assertThat(prefs2.getSignalLostRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testCachedDeviceAddress() {
+        Preferences prefs =
+                Preferences.builder().setCachedDeviceAddress(FIRST_STRING).build();
+        assertThat(prefs.getCachedDeviceAddress()).isEqualTo(FIRST_STRING);
+        assertThat(prefs.toBuilder().build().getCachedDeviceAddress())
+                .isEqualTo(FIRST_STRING);
+
+        Preferences prefs2 =
+                Preferences.builder().setCachedDeviceAddress(SECOND_STRING).build();
+        assertThat(prefs2.getCachedDeviceAddress()).isEqualTo(SECOND_STRING);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testPossibleCachedDeviceAddress() {
+        Preferences prefs =
+                Preferences.builder().setPossibleCachedDeviceAddress(FIRST_STRING).build();
+        assertThat(prefs.getPossibleCachedDeviceAddress()).isEqualTo(FIRST_STRING);
+        assertThat(prefs.toBuilder().build().getPossibleCachedDeviceAddress())
+                .isEqualTo(FIRST_STRING);
+
+        Preferences prefs2 =
+                Preferences.builder().setPossibleCachedDeviceAddress(SECOND_STRING).build();
+        assertThat(prefs2.getPossibleCachedDeviceAddress()).isEqualTo(SECOND_STRING);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSupportedProfileUuids() {
+        Preferences prefs =
+                Preferences.builder().setSupportedProfileUuids(FIRST_BYTES).build();
+        assertThat(prefs.getSupportedProfileUuids()).isEqualTo(FIRST_BYTES);
+        assertThat(prefs.toBuilder().build().getSupportedProfileUuids())
+                .isEqualTo(FIRST_BYTES);
+
+        Preferences prefs2 =
+                Preferences.builder().setSupportedProfileUuids(SECOND_BYTES).build();
+        assertThat(prefs2.getSupportedProfileUuids()).isEqualTo(SECOND_BYTES);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testGattConnectionAndSecretHandshakeNoRetryGattError() {
+        Preferences prefs =
+                Preferences.builder().setGattConnectionAndSecretHandshakeNoRetryGattError(
+                        FIRST_INT_SETS).build();
+        assertThat(prefs.getGattConnectionAndSecretHandshakeNoRetryGattError())
+                .isEqualTo(FIRST_INT_SETS);
+        assertThat(prefs.toBuilder().build().getGattConnectionAndSecretHandshakeNoRetryGattError())
+                .isEqualTo(FIRST_INT_SETS);
+
+        Preferences prefs2 =
+                Preferences.builder().setGattConnectionAndSecretHandshakeNoRetryGattError(
+                        SECOND_INT_SETS).build();
+        assertThat(prefs2.getGattConnectionAndSecretHandshakeNoRetryGattError())
+                .isEqualTo(SECOND_INT_SETS);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    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();
+        assertThat(prefs2.getExtraLoggingInformation()).isEqualTo(SECOND_EXTRA_LOGGING_INFO);
+    }
+}
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 f061115..26d1847 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
@@ -19,10 +19,12 @@
 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 androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.nearby.common.locator.LocatorContextWrapper;
 
@@ -40,8 +42,11 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+
         mLocatorContextWrapper = new LocatorContextWrapper(mContext);
         mFastPairManager = new FastPairManager(mLocatorContextWrapper);
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
     }
 
     @Test
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
new file mode 100644
index 0000000..08a3c7c
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
@@ -0,0 +1,733 @@
+/*
+ * 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 android.accounts.Account;
+import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofkeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairDiscoveryItemParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import service.proto.Cache;
+import service.proto.Data;
+import service.proto.FastPairString.FastPairStrings;
+import service.proto.Rpcs;
+
+public class UtilsTest {
+
+    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 int DEVICE_TYPE = 1;
+    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 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[] 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 int ACTION_URL_TYPE = 1;
+    private static final String APP_NAME = "APP_NAME";
+    private static final int ATTACHMENT_TYPE = 1;
+    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 = 1;
+    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 = 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";
+    private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+    private static final int RSSI = 9;
+    private static final int STATE = 1;
+    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")
+    public void testHappyPathConvertToFastPairDevicesWithAccountKey() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = {
+                genHappyPathFastPairAccountkeyDeviceMetadataParcel()};
+
+        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
+                Utils.convertToFastPairDevicesWithAccountKey(array);
+        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
+        assertThat(deviceWithAccountKey.get(0)).isEqualTo(
+                genHappyPathFastPairDeviceWithAccountKey());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairDevicesWithAccountKeyWithNullArray() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = null;
+        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairDevicesWithAccountKeyWithNullElement() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = {null};
+        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairDevicesWithAccountKeyWithEmptyElementNoCrash() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = {
+                genEmptyFastPairAccountkeyDeviceMetadataParcel()};
+
+        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
+                Utils.convertToFastPairDevicesWithAccountKey(array);
+        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairDevicesWithAccountKeyWithEmptyMetadataDiscoveryNoCrash() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = {
+                genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem()};
+
+        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
+                Utils.convertToFastPairDevicesWithAccountKey(array);
+        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairDevicesWithAccountKeyWithMixedArrayElements() {
+        FastPairAccountKeyDeviceMetadataParcel[] array = {
+                null,
+                genHappyPathFastPairAccountkeyDeviceMetadataParcel(),
+                genEmptyFastPairAccountkeyDeviceMetadataParcel(),
+                genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem()};
+
+        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(3);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathConvertToAccountList() {
+        FastPairEligibleAccountParcel[] array = {genHappyPathFastPairEligibleAccountParcel()};
+
+        List<Account> accountList = Utils.convertToAccountList(array);
+        assertThat(accountList.size()).isEqualTo(1);
+        assertThat(accountList.get(0)).isEqualTo(ELIGIBLE_ACCOUNT_1);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToAccountListNullArray() {
+        FastPairEligibleAccountParcel[] array = null;
+
+        List<Account> accountList = Utils.convertToAccountList(array);
+        assertThat(accountList.size()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToAccountListWithNullElement() {
+        FastPairEligibleAccountParcel[] array = {null};
+
+        List<Account> accountList = Utils.convertToAccountList(array);
+        assertThat(accountList.size()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToAccountListWithEmptyElementNotCrash() {
+        FastPairEligibleAccountParcel[] array =
+                {genEmptyFastPairEligibleAccountParcel()};
+
+        List<Account> accountList = Utils.convertToAccountList(array);
+        assertThat(accountList.size()).isEqualTo(0);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToAccountListWithMixedArrayElements() {
+        FastPairEligibleAccountParcel[] array = {
+                genHappyPathFastPairEligibleAccountParcel(),
+                genEmptyFastPairEligibleAccountParcel(),
+                null,
+                genHappyPathFastPairEligibleAccountParcel()};
+
+        List<Account> accountList = Utils.convertToAccountList(array);
+        assertThat(accountList.size()).isEqualTo(2);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathConvertToGetObservedDeviceResponse() {
+        Rpcs.GetObservedDeviceResponse response =
+                Utils.convertToGetObservedDeviceResponse(
+                        genHappyPathFastPairAntispoofkeyDeviceMetadataParcel());
+        assertThat(response).isEqualTo(genHappyPathObservedDeviceResponse());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToGetObservedDeviceResponseWithNullInput() {
+        assertThat(Utils.convertToGetObservedDeviceResponse(null))
+                .isEqualTo(null);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToGetObservedDeviceResponseWithEmptyInputNotCrash() {
+        Utils.convertToGetObservedDeviceResponse(
+                genEmptyFastPairAntispoofkeyDeviceMetadataParcel());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToGetObservedDeviceResponseWithEmptyDeviceMetadataNotCrash() {
+        Utils.convertToGetObservedDeviceResponse(
+                genFastPairAntispoofkeyDeviceMetadataParcelWithEmptyDeviceMetadata());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testHappyPathConvertToFastPairAccountKeyDeviceMetadata() {
+        FastPairAccountKeyDeviceMetadataParcel metadataParcel =
+                Utils.convertToFastPairAccountKeyDeviceMetadata(genHappyPathFastPairUploadInfo());
+        ensureHappyPathAsExpected(metadataParcel);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairAccountKeyDeviceMetadataWithNullInput() {
+        assertThat(Utils.convertToFastPairAccountKeyDeviceMetadata(null)).isEqualTo(null);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testConvertToFastPairAccountKeyDeviceMetadataWithEmptyFieldsNotCrash() {
+        Utils.convertToFastPairAccountKeyDeviceMetadata(
+                new FastPairUploadInfo(
+                        null /* discoveryItem */,
+                        null /* accountKey */,
+                        null /* sha256AccountKeyPublicAddress */));
+    }
+
+    private static FastPairUploadInfo genHappyPathFastPairUploadInfo() {
+        return new FastPairUploadInfo(
+                genHappyPathStoredDiscoveryItem(),
+                ByteString.copyFrom(ACCOUNT_KEY),
+                ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
+
+    }
+
+    private static void ensureHappyPathAsExpected(
+            FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+        assertThat(metadataParcel.accountKey).isEqualTo(ACCOUNT_KEY);
+        assertThat(metadataParcel.sha256AccountKeyPublicAddress)
+                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+        ensureHappyPathAsExpected(metadataParcel.metadata);
+        ensureHappyPathAsExpected(metadataParcel.discoveryItem);
+    }
+
+    private static void ensureHappyPathAsExpected(FastPairDeviceMetadataParcel metadataParcel) {
+        assertThat(metadataParcel).isNotNull();
+
+        assertThat(metadataParcel.assistantSetupHalfSheet).isEqualTo(ASSISTANT_SETUP_HALFSHEET);
+        assertThat(metadataParcel.assistantSetupNotification).isEqualTo(
+                ASSISTANT_SETUP_NOTIFICATION);
+
+
+        assertThat(metadataParcel.confirmPinDescription).isEqualTo(CONFIRM_PIN_DESCRIPTION);
+        assertThat(metadataParcel.confirmPinTitle).isEqualTo(CONFIRM_PIN_TITLE);
+        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.failConnectGoToSettingsDescription).isEqualTo(
+                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        assertThat(metadataParcel.fastPairTvConnectDeviceNoAccountDescription).isEqualTo(
+                FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION);
+
+        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.retroactivePairingDescription).isEqualTo(
+                RETRO_ACTIVE_PAIRING_DESCRIPTION);
+
+        assertThat(metadataParcel.subsequentPairingDescription).isEqualTo(
+                SUBSEQUENT_PAIRING_DESCRIPTION);
+        assertThat(metadataParcel.syncContactsDescription).isEqualTo(SYNC_CONTACT_DESCRPTION);
+        assertThat(metadataParcel.syncContactsTitle).isEqualTo(SYNC_CONTACTS_TITLE);
+        assertThat(metadataParcel.syncSmsDescription).isEqualTo(SYNC_SMS_DESCRIPTION);
+        assertThat(metadataParcel.syncSmsTitle).isEqualTo(SYNC_SMS_TITLE);
+
+        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.waitLaunchCompanionAppDescription).isEqualTo(
+                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+        /* do we need upload this? */
+        // assertThat(metadataParcel.locale).isEqualTo(LOCALE);
+        // assertThat(metadataParcel.name).isEqualTo(NAME);
+        // assertThat(metadataParcel.downloadCompanionAppDescription).isEqualTo(
+        //        DOWNLOAD_COMPANION_APP_DESCRIPTION);
+        // assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
+        //        OPEN_COMPANION_APP_DESCRIPTION);
+        // assertThat(metadataParcel.triggerDistance).isWithin(DELTA).of(TRIGGER_DISTANCE);
+        // 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.bleTxPower).isEqualTo(BLE_TX_POWER);
+        // assertThat(metadataParcel.image).isEqualTo(IMAGE);
+        // assertThat(metadataParcel.imageUrl).isEqualTo(IMAGE_URL);
+        // assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
+    }
+
+    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.attachmentType).isEqualTo(ATTACHMENT_TYPE);
+        assertThat(itemParcel.authenticationPublicKeySecp256r1)
+                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+        assertThat(itemParcel.bleRecordBytes).isEqualTo(BLE_RECORD_BYTES);
+        assertThat(itemParcel.debugCategory).isEqualTo(DEBUG_CATEGORY);
+        assertThat(itemParcel.debugMessage).isEqualTo(DEBUG_MESSAGE);
+        assertThat(itemParcel.description).isEqualTo(DESCRIPTION);
+        assertThat(itemParcel.deviceName).isEqualTo(DEVICE_NAME);
+        assertThat(itemParcel.displayUrl).isEqualTo(DISPLAY_URL);
+        assertThat(itemParcel.entityId).isEqualTo(ENTITY_ID);
+        assertThat(itemParcel.featureGraphicUrl).isEqualTo(FEATURE_GRAPHIC_URL);
+        assertThat(itemParcel.firstObservationTimestampMillis)
+                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        assertThat(itemParcel.groupId).isEqualTo(GROUP_ID);
+        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.lastUserExperience).isEqualTo(LAST_USER_EXPERIENCE);
+        assertThat(itemParcel.lostMillis).isEqualTo(LOST_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);
+        assertThat(itemParcel.type).isEqualTo(TYPE);
+    }
+
+    private static FastPairEligibleAccountParcel genHappyPathFastPairEligibleAccountParcel() {
+        FastPairEligibleAccountParcel parcel = new FastPairEligibleAccountParcel();
+        parcel.account = ELIGIBLE_ACCOUNT_1;
+        parcel.optIn = true;
+
+        return parcel;
+    }
+
+    private static FastPairEligibleAccountParcel genEmptyFastPairEligibleAccountParcel() {
+        return new FastPairEligibleAccountParcel();
+    }
+
+    private static FastPairDeviceMetadataParcel genEmptyFastPairDeviceMetadataParcel() {
+        return new FastPairDeviceMetadataParcel();
+    }
+
+    private static FastPairDiscoveryItemParcel genEmptyFastPairDiscoveryItemParcel() {
+        return new FastPairDiscoveryItemParcel();
+    }
+
+    private static FastPairAccountKeyDeviceMetadataParcel
+            genEmptyFastPairAccountkeyDeviceMetadataParcel() {
+        return new FastPairAccountKeyDeviceMetadataParcel();
+    }
+
+    private static FastPairAccountKeyDeviceMetadataParcel
+            genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem() {
+        FastPairAccountKeyDeviceMetadataParcel parcel =
+                new FastPairAccountKeyDeviceMetadataParcel();
+        parcel.metadata = genEmptyFastPairDeviceMetadataParcel();
+        parcel.discoveryItem = genEmptyFastPairDiscoveryItemParcel();
+
+        return parcel;
+    }
+
+    private static FastPairAccountKeyDeviceMetadataParcel
+            genHappyPathFastPairAccountkeyDeviceMetadataParcel() {
+        FastPairAccountKeyDeviceMetadataParcel parcel =
+                new FastPairAccountKeyDeviceMetadataParcel();
+        parcel.accountKey = ACCOUNT_KEY;
+        parcel.metadata = genHappyPathFastPairDeviceMetadataParcel();
+        parcel.sha256AccountKeyPublicAddress = SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS;
+        parcel.discoveryItem = genHappyPathFastPairDiscoveryItemParcel();
+
+        return parcel;
+    }
+
+    private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+        FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+        parcel.assistantSetupHalfSheet = ASSISTANT_SETUP_HALFSHEET;
+        parcel.assistantSetupNotification = ASSISTANT_SETUP_NOTIFICATION;
+        parcel.bleTxPower = BLE_TX_POWER;
+        parcel.confirmPinDescription = CONFIRM_PIN_DESCRIPTION;
+        parcel.confirmPinTitle = CONFIRM_PIN_TITLE;
+        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.fastPairTvConnectDeviceNoAccountDescription =
+                FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_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.locale = LOCALE;
+        parcel.name = NAME;
+        parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+        parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+        parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+        parcel.syncContactsDescription = SYNC_CONTACT_DESCRPTION;
+        parcel.syncContactsTitle = SYNC_CONTACTS_TITLE;
+        parcel.syncSmsDescription = SYNC_SMS_DESCRIPTION;
+        parcel.syncSmsTitle = SYNC_SMS_TITLE;
+        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.setAttachmentType(
+                Cache.DiscoveryAttachmentType.DISCOVERY_ATTACHMENT_TYPE_NORMAL);
+        storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
+                ByteString.copyFrom(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1));
+        storedDiscoveryItemBuilder.setBleRecordBytes(ByteString.copyFrom(BLE_RECORD_BYTES));
+        storedDiscoveryItemBuilder.setDebugCategory(
+                Cache.StoredDiscoveryItem.DebugMessageCategory.STATUS_VALID_NOTIFICATION);
+        storedDiscoveryItemBuilder.setDebugMessage(DEBUG_MESSAGE);
+        storedDiscoveryItemBuilder.setDescription(DESCRIPTION);
+        storedDiscoveryItemBuilder.setDeviceName(DEVICE_NAME);
+        storedDiscoveryItemBuilder.setDisplayUrl(DISPLAY_URL);
+        storedDiscoveryItemBuilder.setEntityId(ENTITY_ID);
+        storedDiscoveryItemBuilder.setFeatureGraphicUrl(FEATURE_GRAPHIC_URL);
+        storedDiscoveryItemBuilder.setFirstObservationTimestampMillis(
+                FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+        storedDiscoveryItemBuilder.setGroupId(GROUP_ID);
+        storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
+        storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
+        storedDiscoveryItemBuilder.setId(ID);
+        storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
+                LAST_OBSERVATION_TIMESTAMP_MILLIS);
+        storedDiscoveryItemBuilder.setLastUserExperience(
+                Cache.StoredDiscoveryItem.ExperienceType.EXPERIENCE_GOOD);
+        storedDiscoveryItemBuilder.setLostMillis(LOST_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);
+        storedDiscoveryItemBuilder.setType(Cache.NearbyType.NEARBY_PROXIMITY_BEACON);
+
+        FastPairStrings.Builder stringsBuilder = FastPairStrings.newBuilder();
+        stringsBuilder.setAssistantHalfSheetDescription(ASSISTANT_SETUP_HALFSHEET);
+        stringsBuilder.setAssistantNotificationDescription(ASSISTANT_SETUP_NOTIFICATION);
+        stringsBuilder.setConfirmPinDescription(CONFIRM_PIN_DESCRIPTION);
+        stringsBuilder.setConfirmPinTitle(CONFIRM_PIN_TITLE);
+        stringsBuilder.setPairingFinishedCompanionAppInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+        stringsBuilder.setPairingFinishedCompanionAppNotInstalled(
+                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+        stringsBuilder.setPairingFailDescription(
+                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+        stringsBuilder.setFastPairTvConnectDeviceNoAccountDescription(
+                FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_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.setSyncContactsDescription(SYNC_CONTACT_DESCRPTION);
+        stringsBuilder.setSyncContactsTitle(SYNC_CONTACTS_TITLE);
+        stringsBuilder.setSyncSmsDescription(SYNC_SMS_DESCRIPTION);
+        stringsBuilder.setSyncSmsTitle(SYNC_SMS_TITLE);
+        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 Data.FastPairDeviceWithAccountKey genHappyPathFastPairDeviceWithAccountKey() {
+        Data.FastPairDeviceWithAccountKey.Builder fpDeviceBuilder =
+                Data.FastPairDeviceWithAccountKey.newBuilder();
+        fpDeviceBuilder.setAccountKey(ByteString.copyFrom(ACCOUNT_KEY));
+        fpDeviceBuilder.setSha256AccountKeyPublicAddress(
+                ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
+        fpDeviceBuilder.setDiscoveryItem(genHappyPathStoredDiscoveryItem());
+
+        return fpDeviceBuilder.build();
+    }
+
+    private static FastPairDiscoveryItemParcel genHappyPathFastPairDiscoveryItemParcel() {
+        FastPairDiscoveryItemParcel parcel = new FastPairDiscoveryItemParcel();
+        parcel.actionUrl = ACTION_URL;
+        parcel.actionUrlType = ACTION_URL_TYPE;
+        parcel.appName = APP_NAME;
+        parcel.attachmentType = ATTACHMENT_TYPE;
+        parcel.authenticationPublicKeySecp256r1 = AUTHENTICATION_PUBLIC_KEY_SEC_P256R1;
+        parcel.bleRecordBytes = BLE_RECORD_BYTES;
+        parcel.debugCategory = DEBUG_CATEGORY;
+        parcel.debugMessage = DEBUG_MESSAGE;
+        parcel.description = DESCRIPTION;
+        parcel.deviceName = DEVICE_NAME;
+        parcel.displayUrl = DISPLAY_URL;
+        parcel.entityId = ENTITY_ID;
+        parcel.featureGraphicUrl = FEATURE_GRAPHIC_URL;
+        parcel.firstObservationTimestampMillis = FIRST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.groupId = GROUP_ID;
+        parcel.iconFifeUrl = ICON_FIFE_URL;
+        parcel.iconPng = ICON_PNG;
+        parcel.id = ID;
+        parcel.lastObservationTimestampMillis = LAST_OBSERVATION_TIMESTAMP_MILLIS;
+        parcel.lastUserExperience = LAST_USER_EXPERIENCE;
+        parcel.lostMillis = LOST_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;
+        parcel.type = TYPE;
+
+        return parcel;
+    }
+
+    private static Rpcs.GetObservedDeviceResponse genHappyPathObservedDeviceResponse() {
+        Rpcs.Device.Builder deviceBuilder = Rpcs.Device.newBuilder();
+        deviceBuilder.setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder()
+                .setPublicKey(ByteString.copyFrom(ANTI_SPOOFING_KEY))
+                .build());
+        Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
+                Rpcs.TrueWirelessHeadsetImages.newBuilder();
+        imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+        imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+        imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE);
+        deviceBuilder.setTrueWirelessImages(imagesBuilder.build());
+        deviceBuilder.setImageUrl(IMAGE_URL);
+        deviceBuilder.setIntentUri(INTENT_URI);
+        deviceBuilder.setName(NAME);
+        deviceBuilder.setBleTxPower(BLE_TX_POWER);
+        deviceBuilder.setTriggerDistance(TRIGGER_DISTANCE);
+        deviceBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES);
+
+        return Rpcs.GetObservedDeviceResponse.newBuilder()
+                .setDevice(deviceBuilder.build())
+                .setImage(ByteString.copyFrom(IMAGE))
+                .setStrings(Rpcs.ObservedDeviceStrings.newBuilder()
+                        .setAssistantSetupHalfSheet(ASSISTANT_SETUP_HALFSHEET)
+                        .setAssistantSetupNotification(ASSISTANT_SETUP_NOTIFICATION)
+                        .setConfirmPinDescription(CONFIRM_PIN_DESCRIPTION)
+                        .setConfirmPinTitle(CONFIRM_PIN_TITLE)
+                        .setConnectSuccessCompanionAppInstalled(
+                                CONNECT_SUCCESS_COMPANION_APP_INSTALLED)
+                        .setConnectSuccessCompanionAppNotInstalled(
+                                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED)
+                        .setDownloadCompanionAppDescription(
+                                DOWNLOAD_COMPANION_APP_DESCRIPTION)
+                        .setFailConnectGoToSettingsDescription(
+                                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION)
+                        .setFastPairTvConnectDeviceNoAccountDescription(
+                                FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION)
+                        .setInitialNotificationDescription(
+                                INITIAL_NOTIFICATION_DESCRIPTION)
+                        .setInitialNotificationDescriptionNoAccount(
+                                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT)
+                        .setInitialPairingDescription(
+                                INITIAL_PAIRING_DESCRIPTION)
+                        .setLocale(LOCALE)
+                        .setOpenCompanionAppDescription(
+                                OPEN_COMPANION_APP_DESCRIPTION)
+                        .setRetroactivePairingDescription(
+                                RETRO_ACTIVE_PAIRING_DESCRIPTION)
+                        .setSubsequentPairingDescription(
+                                SUBSEQUENT_PAIRING_DESCRIPTION)
+                        .setSyncContactsDescription(
+                                SYNC_CONTACT_DESCRPTION)
+                        .setSyncContactsTitle(
+                                SYNC_CONTACTS_TITLE)
+                        .setSyncSmsDescription(
+                                SYNC_SMS_DESCRIPTION)
+                        .setSyncSmsTitle(
+                                SYNC_SMS_TITLE)
+                        .setUnableToConnectDescription(
+                                UNABLE_TO_CONNECT_DESCRIPTION)
+                        .setUnableToConnectTitle(
+                                UNABLE_TO_CONNECT_TITLE)
+                        .setUpdateCompanionAppDescription(
+                                UPDATE_COMPANION_APP_DESCRIPTION)
+                        .setWaitLaunchCompanionAppDescription(
+                                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION)
+                        .build())
+                .build();
+    }
+
+    private static FastPairAntispoofkeyDeviceMetadataParcel
+            genHappyPathFastPairAntispoofkeyDeviceMetadataParcel() {
+        FastPairAntispoofkeyDeviceMetadataParcel parcel =
+                new FastPairAntispoofkeyDeviceMetadataParcel();
+        parcel.antiSpoofPublicKey = ANTI_SPOOFING_KEY;
+        parcel.deviceMetadata = genHappyPathFastPairDeviceMetadataParcel();
+
+        return parcel;
+    }
+
+    private static FastPairAntispoofkeyDeviceMetadataParcel
+            genFastPairAntispoofkeyDeviceMetadataParcelWithEmptyDeviceMetadata() {
+        FastPairAntispoofkeyDeviceMetadataParcel parcel =
+                new FastPairAntispoofkeyDeviceMetadataParcel();
+        parcel.antiSpoofPublicKey = ANTI_SPOOFING_KEY;
+        parcel.deviceMetadata = genEmptyFastPairDeviceMetadataParcel();
+
+        return parcel;
+    }
+
+    private static FastPairAntispoofkeyDeviceMetadataParcel
+            genEmptyFastPairAntispoofkeyDeviceMetadataParcel() {
+        return new FastPairAntispoofkeyDeviceMetadataParcel();
+    }
+}