Merge "Add FastPairTestDataProviderService#onManageFastPairAccountDevice logging." into tm-dev
diff --git a/nearby/framework/java/android/nearby/CredentialElement.java b/nearby/framework/java/android/nearby/CredentialElement.java
index d2049d1..7a43b01 100644
--- a/nearby/framework/java/android/nearby/CredentialElement.java
+++ b/nearby/framework/java/android/nearby/CredentialElement.java
@@ -17,12 +17,16 @@
 package android.nearby;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * Represents an element in {@link PresenceCredential}.
  *
@@ -33,12 +37,9 @@
     private final String mKey;
     private final byte[] mValue;
 
-    /**
-     * Constructs a {@link CredentialElement}.
-     */
+    /** 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");
+        Preconditions.checkState(key != null && value != null, "neither key or value can be null");
         mKey = key;
         mValue = value;
     }
@@ -58,7 +59,7 @@
                 public CredentialElement[] newArray(int size) {
                     return new CredentialElement[size];
                 }
-    };
+            };
 
     @Override
     public int describeContents() {
@@ -72,19 +73,29 @@
         dest.writeByteArray(mValue);
     }
 
-    /**
-     * Returns the key of the credential element.
-     */
+    /** Returns the key of the credential element. */
     @NonNull
     public String getKey() {
         return mKey;
     }
 
-    /**
-     * Returns the value of the credential element.
-     */
+    /** Returns the value of the credential element. */
     @NonNull
     public byte[] getValue() {
         return mValue;
     }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj instanceof CredentialElement) {
+            CredentialElement that = (CredentialElement) obj;
+            return mKey.equals(that.mKey) && Arrays.equals(mValue, that.mValue);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mKey.hashCode(), Arrays.hashCode(mValue));
+    }
 }
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 694e15b..1ad3571 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -29,21 +29,18 @@
 
 /**
  * A data class representing scan result from Nearby Service. Scan result can come from multiple
- * mediums like BLE, Wi-Fi Aware, and etc.
- * A scan result  consists of
- * An encapsulation of various parameters for requesting nearby scans.
+ * mediums like BLE, Wi-Fi Aware, and etc. A scan result consists of An encapsulation of various
+ * parameters for requesting nearby scans.
  *
  * <p>All scan results generated through {@link NearbyManager} are guaranteed to have a valid
- * medium, identifier, timestamp (both UTC time and elapsed real-time since boot), and accuracy.
- * All other parameters are optional.
+ * medium, identifier, timestamp (both UTC time and elapsed real-time since boot), and accuracy. All
+ * other parameters are optional.
  *
  * @hide
  */
 public final class NearbyDeviceParcelable implements Parcelable {
 
-    /**
-     * Used to read a NearbyDeviceParcelable from a Parcel.
-     */
+    /** Used to read a NearbyDeviceParcelable from a Parcel. */
     @NonNull
     public static final Creator<NearbyDeviceParcelable> CREATOR =
             new Creator<NearbyDeviceParcelable>() {
@@ -54,8 +51,13 @@
                         builder.setName(in.readString());
                     }
                     builder.setMedium(in.readInt());
-                    builder.setRssi(in.readInt());
                     builder.setTxPower(in.readInt());
+                    builder.setRssi(in.readInt());
+                    builder.setAction(in.readInt());
+                    builder.setPublicCredential(
+                            in.readParcelable(
+                                    PublicCredential.class.getClassLoader(),
+                                    PublicCredential.class));
                     if (in.readInt() == 1) {
                         builder.setFastPairModelId(in.readString());
                     }
@@ -78,36 +80,40 @@
             };
 
     @ScanRequest.ScanType int mScanType;
-    @Nullable
-    private final String mName;
-    @NearbyDevice.Medium
-    private final int mMedium;
-    private final int mRssi;
+    @Nullable private final String mName;
+    @NearbyDevice.Medium private final int mMedium;
     private final int mTxPower;
+    private final int mRssi;
+    private final int mAction;
+    private final PublicCredential mPublicCredential;
+    @Nullable private final String mBluetoothAddress;
+    @Nullable private final String mFastPairModelId;
+    @Nullable private final byte[] mData;
 
-    @Nullable
-    private final String mBluetoothAddress;
-    @Nullable
-    private final String mFastPairModelId;
-    @Nullable
-    private final byte[] mData;
-
-    private NearbyDeviceParcelable(@ScanRequest.ScanType int scanType, @Nullable String name,
-            int medium, int rssi, int txPower, @Nullable String fastPairModelId,
-            @Nullable String bluetoothAddress, @Nullable byte[] data) {
+    private NearbyDeviceParcelable(
+            @ScanRequest.ScanType int scanType,
+            @Nullable String name,
+            int medium,
+            int TxPower,
+            int rssi,
+            int action,
+            PublicCredential publicCredential,
+            @Nullable String fastPairModelId,
+            @Nullable String bluetoothAddress,
+            @Nullable byte[] data) {
         mScanType = scanType;
         mName = name;
         mMedium = medium;
+        mTxPower = TxPower;
         mRssi = rssi;
-        mTxPower = txPower;
+        mAction = action;
+        mPublicCredential = publicCredential;
         mFastPairModelId = fastPairModelId;
         mBluetoothAddress = bluetoothAddress;
         mData = data;
     }
 
-    /**
-     * No special parcel contents.
-     */
+    /** No special parcel contents. */
     @Override
     public int describeContents() {
         return 0;
@@ -119,7 +125,6 @@
      * @param dest The Parcel in which the object should be written.
      * @param flags Additional flags about how the object should be written.
      */
-
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mName == null ? 0 : 1);
@@ -127,8 +132,10 @@
             dest.writeString(mName);
         }
         dest.writeInt(mMedium);
-        dest.writeInt(mRssi);
         dest.writeInt(mTxPower);
+        dest.writeInt(mRssi);
+        dest.writeInt(mAction);
+        dest.writeParcelable(mPublicCredential, flags);
         dest.writeInt(mFastPairModelId == null ? 0 : 1);
         if (mFastPairModelId != null) {
             dest.writeString(mFastPairModelId);
@@ -144,19 +151,26 @@
         }
     }
 
-    /**
-     * Returns a string representation of this ScanRequest.
-     */
+    /** Returns a string representation of this ScanRequest. */
     @Override
     public String toString() {
         return "NearbyDeviceParcelable["
-                + "name=" + mName
-                + ", medium=" + NearbyDevice.mediumToString(mMedium)
-                + ", rssi=" + mRssi
-                + ", txPower=" + mTxPower
-                + ", bluetoothAddress=" + mBluetoothAddress
-                + ", fastPairModelId=" + mFastPairModelId
-                + ", data=" + Arrays.toString(mData)
+                + "name="
+                + mName
+                + ", medium="
+                + NearbyDevice.mediumToString(mMedium)
+                + ", txPower="
+                + mTxPower
+                + ", rssi="
+                + mRssi
+                + ", action="
+                + mAction
+                + ", bluetoothAddress="
+                + mBluetoothAddress
+                + ", fastPairModelId="
+                + mFastPairModelId
+                + ", data="
+                + Arrays.toString(mData)
                 + "]";
     }
 
@@ -164,10 +178,13 @@
     public boolean equals(Object other) {
         if (other instanceof NearbyDeviceParcelable) {
             NearbyDeviceParcelable otherNearbyDeviceParcelable = (NearbyDeviceParcelable) other;
-            return  Objects.equals(mName, otherNearbyDeviceParcelable.mName)
+            return Objects.equals(mName, otherNearbyDeviceParcelable.mName)
                     && (mMedium == otherNearbyDeviceParcelable.mMedium)
-                    && (mRssi == otherNearbyDeviceParcelable.mRssi)
                     && (mTxPower == otherNearbyDeviceParcelable.mTxPower)
+                    && (mRssi == otherNearbyDeviceParcelable.mRssi)
+                    && (mAction == otherNearbyDeviceParcelable.mAction)
+                    && (Objects.equals(
+                            mPublicCredential, otherNearbyDeviceParcelable.mPublicCredential))
                     && (Objects.equals(
                             mBluetoothAddress, otherNearbyDeviceParcelable.mBluetoothAddress))
                     && (Objects.equals(
@@ -180,8 +197,14 @@
     @Override
     public int hashCode() {
         return Objects.hash(
-                mName, mMedium, mRssi, mTxPower, mBluetoothAddress,
-                mFastPairModelId, Arrays.hashCode(mData));
+                mName,
+                mMedium,
+                mRssi,
+                mAction,
+                mPublicCredential.hashCode(),
+                mBluetoothAddress,
+                mFastPairModelId,
+                Arrays.hashCode(mData));
     }
 
     /**
@@ -194,9 +217,7 @@
         return mScanType;
     }
 
-    /**
-     * Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name.
-     */
+    /** Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name. */
     @Nullable
     public String getName() {
         return mName;
@@ -212,21 +233,39 @@
     }
 
     /**
-     * Gets the received signal strength in dBm.
+     * Gets the transmission power in dBm.
+     *
+     * @hide
      */
     @IntRange(from = -127, to = 126)
+    public int getTxPower() {
+        return mTxPower;
+    }
+
+    /** Gets the received signal strength in dBm. */
+    @IntRange(from = -127, to = 126)
     public int getRssi() {
         return mRssi;
     }
 
     /**
-     * Gets the transmit power in dBm. A value of
-     * android.bluetooth.le.ScanResult#TX_POWER_NOT_PRESENT
-     * indicates that the TX power is not present.
+     * Gets the Action.
+     *
+     * @hide
      */
     @IntRange(from = -127, to = 126)
-    public int getTxPower() {
-        return mTxPower;
+    public int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Gets the public credential.
+     *
+     * @hide
+     */
+    @NonNull
+    public PublicCredential getPublicCredential() {
+        return mPublicCredential;
     }
 
     /**
@@ -247,31 +286,24 @@
         return mBluetoothAddress;
     }
 
-    /**
-     * Gets the raw data from the scanning. Returns {@code null} if there is no extra data.
-     */
+    /** Gets the raw data from the scanning. Returns {@code null} if there is no extra data. */
     @Nullable
     public byte[] getData() {
         return mData;
     }
 
-    /**
-     * Builder class for {@link NearbyDeviceParcelable}.
-     */
+    /** Builder class for {@link NearbyDeviceParcelable}. */
     public static final class Builder {
-        @Nullable
-        private String mName;
-        @NearbyDevice.Medium
-        private int mMedium;
+        @Nullable private String mName;
+        @NearbyDevice.Medium private int mMedium;
+        private int mTxPower;
         private int mRssi;
-        private int mTxPower = ScanResult.TX_POWER_NOT_PRESENT;
+        private int mAction;
+        private PublicCredential mPublicCredential;
         @ScanRequest.ScanType int mScanType;
-        @Nullable
-        private String mFastPairModelId;
-        @Nullable
-        private String mBluetoothAddress;
-        @Nullable
-        private byte[] mData;
+        @Nullable private String mFastPairModelId;
+        @Nullable private String mBluetoothAddress;
+        @Nullable private byte[] mData;
 
         /**
          * Sets the scan type of the NearbyDeviceParcelable.
@@ -306,6 +338,18 @@
         }
 
         /**
+         * Sets the transmission power of the discovered device.
+         *
+         * @param txPower The transmission power in dBm.
+         * @hide
+         */
+        @NonNull
+        public Builder setTxPower(int txPower) {
+            mTxPower = txPower;
+            return this;
+        }
+
+        /**
          * Sets the RSSI between scanned device and the discovered device.
          *
          * @param rssi The received signal strength in dBm.
@@ -317,13 +361,26 @@
         }
 
         /**
-         * Sets the txPower.
+         * Sets the action from the discovered device.
          *
-         * @param txPower The transmit power in dBm
+         * @param action The action of the discovered device.
+         * @hide
          */
         @NonNull
-        public Builder setTxPower(@IntRange(from = -127, to = 126) int txPower) {
-            mTxPower = txPower;
+        public Builder setAction(int action) {
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Sets the public credential of the discovered device.
+         *
+         * @param publicCredential The public credential.
+         * @hide
+         */
+        @NonNull
+        public Builder setPublicCredential(@NonNull PublicCredential publicCredential) {
+            mPublicCredential = publicCredential;
             return this;
         }
 
@@ -352,8 +409,8 @@
         /**
          * Sets the scanned raw data.
          *
-         * @param data Data the scan.
-         * For example, {@link ScanRecord#getServiceData()} if scanned by Bluetooth.
+         * @param data Data the scan. For example, {@link ScanRecord#getServiceData()} if scanned by
+         *     Bluetooth.
          */
         @NonNull
         public Builder setData(@Nullable byte[] data) {
@@ -361,13 +418,20 @@
             return this;
         }
 
-        /**
-         * Builds a ScanResult.
-         */
+        /** Builds a ScanResult. */
         @NonNull
         public NearbyDeviceParcelable build() {
-            return new NearbyDeviceParcelable(mScanType, mName, mMedium, mRssi, mTxPower,
-                    mFastPairModelId, mBluetoothAddress, mData);
+            return new NearbyDeviceParcelable(
+                    mScanType,
+                    mName,
+                    mMedium,
+                    mTxPower,
+                    mRssi,
+                    mAction,
+                    mPublicCredential,
+                    mFastPairModelId,
+                    mBluetoothAddress,
+                    mData);
         }
     }
 }
diff --git a/nearby/framework/java/android/nearby/PresenceCredential.java b/nearby/framework/java/android/nearby/PresenceCredential.java
index 7ad6ae9..1e851d6 100644
--- a/nearby/framework/java/android/nearby/PresenceCredential.java
+++ b/nearby/framework/java/android/nearby/PresenceCredential.java
@@ -24,7 +24,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Represents a credential for Nearby Presence.
@@ -33,50 +35,43 @@
  */
 @SystemApi
 public abstract class PresenceCredential {
-    /**
-     * Private credential type.
-     */
+    /** Private credential type. */
     public static final int CREDENTIAL_TYPE_PRIVATE = 0;
 
-    /**
-     * Public credential type.
-     */
+    /** Public credential type. */
     public static final int CREDENTIAL_TYPE_PUBLIC = 1;
 
-    /** @hide **/
+    /**
+     * @hide *
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({CREDENTIAL_TYPE_PUBLIC, CREDENTIAL_TYPE_PRIVATE})
-    public @interface CredentialType {
-    }
+    public @interface CredentialType {}
 
-    /**
-     * Unknown identity type.
-     */
+    /** Unknown identity type. */
     public static final int IDENTITY_TYPE_UNKNOWN = 0;
 
-    /**
-     * Private identity type.
-     */
+    /** Private identity type. */
     public static final int IDENTITY_TYPE_PRIVATE = 1;
-    /**
-     * Provisioned identity type.
-     */
+    /** Provisioned identity type. */
     public static final int IDENTITY_TYPE_PROVISIONED = 2;
-    /**
-     * Trusted identity type.
-     */
+    /** Trusted identity type. */
     public static final int IDENTITY_TYPE_TRUSTED = 3;
-    /**
-     * Public identity type.
-     */
+    /** Public identity type. */
     public static final int IDENTITY_TYPE_PUBLIC = 4;
 
-    /** @hide **/
+    /**
+     * @hide *
+     */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({IDENTITY_TYPE_UNKNOWN, IDENTITY_TYPE_PRIVATE, IDENTITY_TYPE_PROVISIONED,
-            IDENTITY_TYPE_TRUSTED, IDENTITY_TYPE_PUBLIC})
-    public @interface IdentityType {
-    }
+    @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;
@@ -84,8 +79,12 @@
     private final byte[] mAuthenticityKey;
     private final List<CredentialElement> mCredentialElements;
 
-    PresenceCredential(@CredentialType int type, @IdentityType int identityType,
-            byte[] secretId, byte[] authenticityKey, List<CredentialElement> credentialElements) {
+    PresenceCredential(
+            @CredentialType int type,
+            @IdentityType int identityType,
+            byte[] secretId,
+            byte[] authenticityKey,
+            List<CredentialElement> credentialElements) {
         mType = type;
         mIdentityType = identityType;
         mSecretId = secretId;
@@ -101,48 +100,63 @@
         mAuthenticityKey = new byte[in.readInt()];
         in.readByteArray(mAuthenticityKey);
         mCredentialElements = new ArrayList<>();
-        in.readList(mCredentialElements, CredentialElement.class.getClassLoader(),
+        in.readList(
+                mCredentialElements,
+                CredentialElement.class.getClassLoader(),
                 CredentialElement.class);
     }
 
-    /**
-     * Returns the type of the credential.
-     */
+    /** Returns the type of the credential. */
     public @CredentialType int getType() {
         return mType;
     }
 
-    /**
-     * Returns the identity type of the credential.
-     */
+    /** Returns the identity type of the credential. */
     public @IdentityType int getIdentityType() {
         return mIdentityType;
     }
 
-    /**
-     * Returns the secret id of the credential.
-     */
+    /** Returns the secret id of the credential. */
     @NonNull
     public byte[] getSecretId() {
         return mSecretId;
     }
 
-    /**
-     * Returns the authenticity key of the credential.
-     */
+    /** Returns the authenticity key of the credential. */
     @NonNull
     public byte[] getAuthenticityKey() {
         return mAuthenticityKey;
     }
 
-    /**
-     * Returns the elements of the credential.
-     */
+    /** Returns the elements of the credential. */
     @NonNull
     public List<CredentialElement> getCredentialElements() {
         return mCredentialElements;
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof PresenceCredential) {
+            PresenceCredential that = (PresenceCredential) obj;
+            return mType == that.mType
+                    && mIdentityType == that.mIdentityType
+                    && Arrays.equals(mSecretId, that.mSecretId)
+                    && Arrays.equals(mAuthenticityKey, that.mAuthenticityKey)
+                    && mCredentialElements.equals(that.mCredentialElements);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mType,
+                mIdentityType,
+                Arrays.hashCode(mSecretId),
+                Arrays.hashCode(mAuthenticityKey),
+                mCredentialElements.hashCode());
+    }
+
     /**
      * Writes the presence credential to the parcel.
      *
diff --git a/nearby/framework/java/android/nearby/PublicCredential.java b/nearby/framework/java/android/nearby/PublicCredential.java
index 8aac323..5998d19 100644
--- a/nearby/framework/java/android/nearby/PublicCredential.java
+++ b/nearby/framework/java/android/nearby/PublicCredential.java
@@ -17,6 +17,7 @@
 package android.nearby;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -24,6 +25,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -35,25 +37,31 @@
 @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);
-        }
+    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];
-        }
-    };
+                @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,
+    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;
@@ -75,25 +83,19 @@
         return new PublicCredential(in);
     }
 
-    /**
-     * Returns the public key associated with this credential.
-     */
+    /** Returns the public key associated with this credential. */
     @NonNull
     public byte[] getPublicKey() {
         return mPublicKey;
     }
 
-    /**
-     * Returns the encrypted metadata associated with this credential.
-     */
+    /** Returns the encrypted metadata associated with this credential. */
     @NonNull
     public byte[] getEncryptedMetadata() {
         return mEncryptedMetadata;
     }
 
-    /**
-     * Returns the metadata encryption key tag associated with this credential.
-     */
+    /** Returns the metadata encryption key tag associated with this credential. */
     @NonNull
     public byte[] getEncryptedMetadataKeyTag() {
         return mEncryptedMetadataKeyTag;
@@ -105,6 +107,27 @@
     }
 
     @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj instanceof PublicCredential) {
+            PublicCredential that = (PublicCredential) obj;
+            return super.equals(obj)
+                    && Arrays.equals(mPublicKey, that.mPublicKey)
+                    && Arrays.equals(mEncryptedMetadata, that.mEncryptedMetadata)
+                    && Arrays.equals(mEncryptedMetadataKeyTag, that.mEncryptedMetadataKeyTag);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                super.hashCode(),
+                Arrays.hashCode(mPublicKey),
+                Arrays.hashCode(mEncryptedMetadata),
+                Arrays.hashCode(mEncryptedMetadataKeyTag));
+    }
+
+    @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(mPublicKey.length);
@@ -115,9 +138,7 @@
         dest.writeByteArray(mEncryptedMetadataKeyTag);
     }
 
-    /**
-     * Builder class for {@link PresenceCredential}.
-     */
+    /** Builder class for {@link PresenceCredential}. */
     public static final class Builder {
         private final List<CredentialElement> mCredentialElements;
 
@@ -128,17 +149,21 @@
         private final byte[] mEncryptedMetadata;
         private final byte[] mEncryptedMetadataKeyTag;
 
-        public Builder(@NonNull byte[] secretId, @NonNull byte[] authenticityKey,
-                @NonNull byte[] publicKey, @NonNull byte[] encryptedMetadata,
+        public Builder(
+                @NonNull byte[] secretId,
+                @NonNull byte[] authenticityKey,
+                @NonNull byte[] publicKey,
+                @NonNull byte[] encryptedMetadata,
                 @NonNull byte[] encryptedMetadataKeyTag) {
-            Preconditions.checkState(secretId != null && secretId.length > 0,
-                    "secret id cannot be empty");
-            Preconditions.checkState(authenticityKey != null && authenticityKey.length > 0,
+            Preconditions.checkState(
+                    secretId != null && secretId.length > 0, "secret id cannot be empty");
+            Preconditions.checkState(
+                    authenticityKey != null && authenticityKey.length > 0,
                     "authenticity key cannot be empty");
             Preconditions.checkState(
-                    publicKey != null && publicKey.length > 0,
-                    "publicKey cannot be empty");
-            Preconditions.checkState(encryptedMetadata != null && encryptedMetadata.length > 0,
+                    publicKey != null && publicKey.length > 0, "publicKey cannot be empty");
+            Preconditions.checkState(
+                    encryptedMetadata != null && encryptedMetadata.length > 0,
                     "encryptedMetadata cannot be empty");
             Preconditions.checkState(
                     encryptedMetadataKeyTag != null && encryptedMetadataKeyTag.length > 0,
@@ -152,18 +177,14 @@
             mCredentialElements = new ArrayList<>();
         }
 
-        /**
-         * Sets the identity type for the presence credential.
-         */
+        /** 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.
-         */
+        /** Adds an element to the credential. */
         @NonNull
         public Builder addCredentialElement(@NonNull CredentialElement credentialElement) {
             Objects.requireNonNull(credentialElement);
@@ -171,14 +192,17 @@
             return this;
         }
 
-        /**
-         * Builds the {@link PresenceCredential}.
-         */
+        /** Builds the {@link PresenceCredential}. */
         @NonNull
         public PublicCredential build() {
-            return new PublicCredential(mIdentityType, mSecretId, mAuthenticityKey,
-                    mCredentialElements, mPublicKey, mEncryptedMetadata, mEncryptedMetadataKeyTag);
+            return new PublicCredential(
+                    mIdentityType,
+                    mSecretId,
+                    mAuthenticityKey,
+                    mCredentialElements,
+                    mPublicKey,
+                    mEncryptedMetadata,
+                    mEncryptedMetadataKeyTag);
         }
-
     }
 }
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl
similarity index 79%
rename from nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl
rename to nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl
index 4f666bc..9200a9d 100644
--- a/nearby/framework/java/android/nearby/aidl/IFastPairClient.aidl
+++ b/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl
@@ -25,11 +25,13 @@
  *
  * {@hide}
  */
-interface IFastPairClient {
+interface IFastPairUiService {
 
-    void registerHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
+    void registerCallback(in IFastPairStatusCallback fastPairStatusCallback);
 
-    void unregisterHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
+    void unregisterCallback(in IFastPairStatusCallback fastPairStatusCallback);
 
     void connect(in FastPairDevice fastPairDevice);
+
+    void cancel(in FastPairDevice fastPairDevice);
 }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/FastPairClient.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java
similarity index 67%
rename from nearby/framework/java/android/nearby/FastPairClient.java
rename to nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java
index dc70d71..bec0c0a 100644
--- a/nearby/framework/java/android/nearby/FastPairClient.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java
@@ -14,53 +14,60 @@
  * limitations under the License.
  */
 
-package android.nearby;
+package com.android.nearby.halfsheet;
 
-import android.annotation.BinderThread;
 import android.content.Context;
-import android.nearby.aidl.IFastPairClient;
+import android.nearby.FastPairDevice;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.PairStatusMetadata;
 import android.nearby.aidl.IFastPairStatusCallback;
+import android.nearby.aidl.IFastPairUiService;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.UiThread;
+
 import java.lang.ref.WeakReference;
 
 /**
- * 0p API for controlling Fast Pair. It communicates between main thread and service.
+ *  A utility class for connecting to the {@link IFastPairUiService} and receive callbacks.
  *
  * @hide
  */
-public class FastPairClient {
+@UiThread
+public class FastPairUiServiceClient {
 
-    private static final String TAG = "FastPairClient";
+    private static final String TAG = "FastPairHalfSheet";
+
     private final IBinder mBinder;
     private final WeakReference<Context> mWeakContext;
-    IFastPairClient mFastPairClient;
+    IFastPairUiService mFastPairUiService;
     PairStatusCallbackIBinder mPairStatusCallbackIBinder;
 
     /**
      * The Ibinder instance should be from
-     * {@link com.android.server.nearby.fastpair.halfsheet.FastPairService} so that the client can
+     * {@link com.android.server.nearby.fastpair.halfsheet.FastPairUiServiceImpl} so that the client can
      * talk with the service.
      */
-    public FastPairClient(Context context, IBinder binder) {
+    public FastPairUiServiceClient(Context context, IBinder binder) {
         mBinder = binder;
-        mFastPairClient = IFastPairClient.Stub.asInterface(mBinder);
+        mFastPairUiService = IFastPairUiService.Stub.asInterface(mBinder);
         mWeakContext = new WeakReference<>(context);
     }
 
     /**
      * Registers a callback at service to get UI updates.
      */
-    public void registerHalfSheet(FastPairStatusCallback fastPairStatusCallback) {
+    public void registerHalfSheetStateCallBack(FastPairStatusCallback fastPairStatusCallback) {
         if (mPairStatusCallbackIBinder != null) {
             return;
         }
         mPairStatusCallbackIBinder = new PairStatusCallbackIBinder(fastPairStatusCallback);
         try {
-            mFastPairClient.registerHalfSheet(mPairStatusCallbackIBinder);
+            mFastPairUiService.registerCallback(mPairStatusCallbackIBinder);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to register fastPairStatusCallback", e);
         }
@@ -71,7 +78,18 @@
      */
     public void connect(FastPairDevice fastPairDevice) {
         try {
-            mFastPairClient.connect(fastPairDevice);
+            mFastPairUiService.connect(fastPairDevice);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
+        }
+    }
+
+    /**
+     * Cancels Fast Pair connection and dismisses half sheet.
+     */
+    public void cancel(FastPairDevice fastPairDevice) {
+        try {
+            mFastPairUiService.cancel(fastPairDevice);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
         }
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index 9507b9b..2a38b8a 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -48,7 +48,7 @@
  */
 public class HalfSheetActivity extends FragmentActivity {
 
-    public static final String TAG = "HalfSheetActivity";
+    public static final String TAG = "FastPairHalfSheet";
 
     public static final String EXTRA_HALF_SHEET_CONTENT =
             "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
index a62c8cc..320965b 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -38,7 +38,6 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.nearby.FastPairClient;
 import android.nearby.FastPairDevice;
 import android.nearby.FastPairStatusCallback;
 import android.nearby.NearbyDevice;
@@ -58,6 +57,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.nearby.halfsheet.FastPairUiServiceClient;
 import com.android.nearby.halfsheet.HalfSheetActivity;
 import com.android.nearby.halfsheet.R;
 import com.android.nearby.halfsheet.utils.FastPairUtils;
@@ -92,7 +92,7 @@
     private Bundle mBundle;
 
     private ScanFastPairStoreItem mScanFastPairStoreItem;
-    private FastPairClient mFastPairClient;
+    private FastPairUiServiceClient mFastPairUiServiceClient;
 
     private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN;
     // True when there is a companion app to open.
@@ -179,8 +179,9 @@
         byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
         mBundle = args.getBundle(EXTRA_BUNDLE);
         if (mBundle != null) {
-            mFastPairClient = new FastPairClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
-            mFastPairClient.registerHalfSheet(this);
+            mFastPairUiServiceClient =
+                    new FastPairUiServiceClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
+            mFastPairUiServiceClient.registerHalfSheetStateCallBack(this);
         }
         if (args.containsKey(ARG_FRAGMENT_STATE)) {
             mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
@@ -293,7 +294,7 @@
         }
         mIsConnecting = true;
         invalidateState();
-        mFastPairClient.connect(
+        mFastPairUiServiceClient.connect(
                 new FastPairDevice.Builder()
                         .addMedium(NearbyDevice.Medium.BLE)
                         .setBluetoothAddress(mScanFastPairStoreItem.getAddress())
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index d721575..e3e5b5d 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -39,16 +39,11 @@
 import com.android.server.nearby.fastpair.FastPairManager;
 import com.android.server.nearby.injector.ContextHubManagerAdapter;
 import com.android.server.nearby.injector.Injector;
-import com.android.server.nearby.presence.ChreCommunication;
 import com.android.server.nearby.presence.PresenceManager;
 import com.android.server.nearby.provider.BroadcastProviderManager;
 import com.android.server.nearby.provider.DiscoveryProviderManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
 
-import java.util.concurrent.Executors;
-
-import service.proto.Blefilter;
-
 /** Service implementing nearby functionality. */
 public class NearbyService extends INearbyManager.Stub {
     public static final String TAG = "NearbyService";
@@ -84,19 +79,7 @@
         mBroadcastProviderManager = new BroadcastProviderManager(context, mSystemInjector);
         final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
         mFastPairManager = new FastPairManager(lcw);
-        mPresenceManager =
-                new PresenceManager(
-                        mContext,
-                        (results) -> {
-                            // TODO(b/221082271): hooked with API codes.
-                            for (Blefilter.BleFilterResult result : results.getResultList()) {
-                                Log.i(
-                                        TAG,
-                                        String.format(
-                                                "received filter result with id: %d",
-                                                result.getId()));
-                            }
-                        });
+        mPresenceManager = new PresenceManager(lcw);
     }
 
     @Override
@@ -144,10 +127,9 @@
                         mBluetoothReceiver,
                         new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
                 mFastPairManager.initiate();
+                // Initialize ContextManager for CHRE scan.
                 mSystemInjector.initializeContextHubManagerAdapter();
-                mPresenceManager.initiate(
-                        new ChreCommunication(
-                                mSystemInjector, Executors.newSingleThreadExecutor()));
+                mPresenceManager.initiate();
                 break;
         }
     }
@@ -155,7 +137,7 @@
     private static final class SystemInjector implements Injector {
         private final Context mContext;
         @Nullable private BluetoothAdapter mBluetoothAdapter;
-        @Nullable private  ContextHubManagerAdapter mContextHubManagerAdapter;
+        @Nullable private ContextHubManagerAdapter mContextHubManagerAdapter;
 
         SystemInjector(Context context) {
             mContext = context;
@@ -168,6 +150,7 @@
         }
 
         @Override
+        @Nullable
         public ContextHubManagerAdapter getContextHubManagerAdapter() {
             return mContextHubManagerAdapter;
         }
@@ -193,6 +176,5 @@
             }
             mContextHubManagerAdapter = new ContextHubManagerAdapter(manager);
         }
-
     }
 }
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 6f79e6e..553d5ce 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
@@ -59,7 +59,7 @@
     private String mHalfSheetApkPkgName;
     private final LocatorContextWrapper mLocatorContextWrapper;
 
-    FastPairService mFastPairService;
+    FastPairUiServiceImpl mFastPairUiService;
 
     public FastPairHalfSheetManager(Context context) {
         this(new LocatorContextWrapper(context));
@@ -68,7 +68,7 @@
     @VisibleForTesting
     FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
         mLocatorContextWrapper = locatorContextWrapper;
-        mFastPairService = new FastPairService();
+        mFastPairUiService = new FastPairUiServiceImpl();
     }
 
     /**
@@ -83,10 +83,10 @@
                     Log.e(TAG, "package name is null");
                     return;
                 }
-                mFastPairService.setFastPairController(
+                mFastPairUiService.setFastPairController(
                         mLocatorContextWrapper.getLocator().get(FastPairController.class));
                 Bundle bundle = new Bundle();
-                bundle.putBinder(EXTRA_BINDER, mFastPairService);
+                bundle.putBinder(EXTRA_BINDER, mFastPairUiService);
                 mLocatorContextWrapper
                         .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
                                         .putExtra(EXTRA_HALF_SHEET_INFO,
@@ -107,7 +107,7 @@
      * Shows pairing fail half sheet.
      */
     public void showPairingFailed() {
-        FastPairStatusCallback pairStatusCallback = mFastPairService.getPairStatusCallback();
+        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
         if (pairStatusCallback != null) {
             Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL");
             pairStatusCallback.onPairUpdate(new FastPairDevice.Builder().build(),
@@ -142,7 +142,7 @@
      * Shows pairing success info.
      */
     public void showPairingSuccessHalfSheet(String address) {
-        FastPairStatusCallback pairStatusCallback = mFastPairService.getPairStatusCallback();
+        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
         if (pairStatusCallback != null) {
             pairStatusCallback.onPairUpdate(
                     new FastPairDevice.Builder().setBluetoothAddress(address).build(),
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
similarity index 86%
rename from nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
rename to nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
index 76eabbf..3bd273e 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
@@ -21,8 +21,8 @@
 import android.nearby.FastPairDevice;
 import android.nearby.FastPairStatusCallback;
 import android.nearby.PairStatusMetadata;
-import android.nearby.aidl.IFastPairClient;
 import android.nearby.aidl.IFastPairStatusCallback;
+import android.nearby.aidl.IFastPairUiService;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -34,7 +34,7 @@
  *
  * @hide
  */
-public class FastPairService extends IFastPairClient.Stub {
+public class FastPairUiServiceImpl extends IFastPairUiService.Stub {
 
     private IBinder mStatusCallbackProxy;
     private FastPairController mFastPairController;
@@ -45,7 +45,7 @@
      * in the server.
      */
     @Override
-    public void registerHalfSheet(IFastPairStatusCallback iFastPairStatusCallback) {
+    public void registerCallback(IFastPairStatusCallback iFastPairStatusCallback) {
         mStatusCallbackProxy = iFastPairStatusCallback.asBinder();
         mFastPairStatusCallback = new FastPairStatusCallback() {
             @Override
@@ -64,7 +64,7 @@
      * Unregisters the Binder call back in the server.
      */
     @Override
-    public void unregisterHalfSheet(IFastPairStatusCallback iFastPairStatusCallback) {
+    public void unregisterCallback(IFastPairStatusCallback iFastPairStatusCallback) {
         mStatusCallbackProxy = null;
         mFastPairStatusCallback = null;
     }
@@ -81,6 +81,13 @@
         }
     }
 
+    /**
+     * Cancels Fast Pair connection and dismisses half sheet.
+     */
+    @Override
+    public void cancel(FastPairDevice fastPairDevice) {
+    }
+
     public FastPairStatusCallback getPairStatusCallback() {
         return mFastPairStatusCallback;
     }
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
index e48074d..80ad88d 100644
--- a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
@@ -17,6 +17,7 @@
 package com.android.server.nearby.presence;
 
 import android.nearby.NearbyDevice;
+import android.nearby.NearbyDeviceParcelable;
 import android.nearby.PresenceDevice;
 import android.nearby.PresenceScanFilter;
 import android.nearby.PublicCredential;
@@ -24,16 +25,17 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * Represents a Presence discovery result.
- */
+/** Represents a Presence discovery result. */
 public class PresenceDiscoveryResult {
 
-    /**
-     * Creates a {@link PresenceDiscoveryResult} from the scan data.
-     */
-    public static PresenceDiscoveryResult fromScanData(byte[] scanData, int rssi) {
-        return new PresenceDiscoveryResult.Builder().setRssi(rssi).build();
+    /** Creates a {@link PresenceDiscoveryResult} from the scan data. */
+    public static PresenceDiscoveryResult fromDevice(NearbyDeviceParcelable device) {
+        return new PresenceDiscoveryResult.Builder()
+                .setTxPower(device.getTxPower())
+                .setRssi(device.getRssi())
+                .addPresenceAction(device.getAction())
+                .setPublicCredential(device.getPublicCredential())
+                .build();
     }
 
     private final int mTxPower;
@@ -42,8 +44,12 @@
     private final List<Integer> mPresenceActions;
     private final PublicCredential mPublicCredential;
 
-    private PresenceDiscoveryResult(int txPower, int rssi, byte[] salt,
-            List<Integer> presenceActions, PublicCredential publicCredential) {
+    private PresenceDiscoveryResult(
+            int txPower,
+            int rssi,
+            byte[] salt,
+            List<Integer> presenceActions,
+            PublicCredential publicCredential) {
         mTxPower = txPower;
         mRssi = rssi;
         mSalt = salt;
@@ -51,9 +57,7 @@
         mPublicCredential = publicCredential;
     }
 
-    /**
-     * Returns whether the discovery result matches the scan filter.
-     */
+    /** Returns whether the discovery result matches the scan filter. */
     public boolean matches(PresenceScanFilter scanFilter) {
         return pathLossMatches(scanFilter.getMaxPathLoss())
                 && actionMatches(scanFilter.getPresenceActions())
@@ -75,9 +79,7 @@
         return credentials.contains(mPublicCredential);
     }
 
-    /**
-     * Converts a presence device from the discovery result.
-     */
+    /** Converts a presence device from the discovery result. */
     public PresenceDevice toPresenceDevice() {
         return new PresenceDevice.Builder(
                 // Use the public credential hash as the device Id.
@@ -86,12 +88,11 @@
                 mPublicCredential.getSecretId(),
                 mPublicCredential.getEncryptedMetadata())
                 .setRssi(mRssi)
-                .addMedium(NearbyDevice.Medium.BLE).build();
+                .addMedium(NearbyDevice.Medium.BLE)
+                .build();
     }
 
-    /**
-     * Builder for {@link PresenceDiscoveryResult}.
-     */
+    /** Builder for {@link PresenceDiscoveryResult}. */
     public static class Builder {
         private int mTxPower;
         private int mRssi;
@@ -104,52 +105,40 @@
             mPresenceActions = new ArrayList<>();
         }
 
-        /**
-         * Sets the calibrated tx power for the discovery result.
-         */
+        /** Sets the calibrated tx power for the discovery result. */
         public Builder setTxPower(int txPower) {
             mTxPower = txPower;
             return this;
         }
 
-        /**
-         * Sets the rssi for the discovery result.
-         */
+        /** Sets the rssi for the discovery result. */
         public Builder setRssi(int rssi) {
             mRssi = rssi;
             return this;
         }
 
-        /**
-         * Sets the salt for the discovery result.
-         */
+        /** Sets the salt for the discovery result. */
         public Builder setSalt(byte[] salt) {
             mSalt = salt;
             return this;
         }
 
-        /**
-         * Sets the public credential for the discovery result.
-         */
+        /** Sets the public credential for the discovery result. */
         public Builder setPublicCredential(PublicCredential publicCredential) {
             mPublicCredential = publicCredential;
             return this;
         }
 
-        /**
-         * Adds presence action of the discovery result.
-         */
+        /** Adds presence action of the discovery result. */
         public Builder addPresenceAction(int presenceAction) {
             mPresenceActions.add(presenceAction);
             return this;
         }
 
-        /**
-         * Builds a {@link PresenceDiscoveryResult}.
-         */
+        /** Builds a {@link PresenceDiscoveryResult}. */
         public PresenceDiscoveryResult build() {
-            return new PresenceDiscoveryResult(mTxPower, mRssi, mSalt, mPresenceActions,
-                    mPublicCredential);
+            return new PresenceDiscoveryResult(
+                    mTxPower, mRssi, mSalt, mPresenceActions, mPublicCredential);
         }
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java
index 66d4864..382c47a 100644
--- a/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java
@@ -16,152 +16,121 @@
 
 package com.android.server.nearby.presence;
 
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.hardware.location.NanoAppMessage;
+import android.nearby.NearbyDevice;
+import android.nearby.NearbyManager;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanCallback;
+import android.nearby.ScanRequest;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.NonNull;
 
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
 
-import java.util.Collections;
-
-import service.proto.Blefilter;
+import java.util.Locale;
+import java.util.concurrent.Executors;
 
 /** PresenceManager is the class initiated in nearby service to handle presence related work. */
 public class PresenceManager {
-    /** Callback that receives filter results from CHRE Nanoapp. */
-    public interface PresenceCallback {
-        /** Called when {@link BleFilterResults} has been received. */
-        void onFilterResults(Blefilter.BleFilterResults filterResults);
-    }
 
-    private static final String TAG = "PresenceService";
-    // Nanoapp ID reserved for Nearby Presence.
-    /** @hide */
-    @VisibleForTesting
-    public static final long NANOAPP_ID = 0x476f6f676c001031L;
-    /** @hide */
-    @VisibleForTesting
-    public static final int NANOAPP_MESSAGE_TYPE_FILTER = 3;
-    /** @hide */
-    @VisibleForTesting
-    public static final int NANOAPP_MESSAGE_TYPE_FILTER_RESULT = 4;
-    private final Context mContext;
-    private final PresenceCallback mPresenceCallback;
-    private final ChreCallback mChreCallback;
-    private ChreCommunication mChreCommunication;
-
-    private Blefilter.BleFilters mFilters = null;
-    private boolean mChreStarted = false;
-
+    final LocatorContextWrapper mLocatorContextWrapper;
+    final Locator mLocator;
     private final IntentFilter mIntentFilter;
 
+    private final ScanCallback mScanCallback =
+            new ScanCallback() {
+                @Override
+                public void onDiscovered(@NonNull NearbyDevice device) {
+                    Log.i(TAG, "[PresenceManager] discovered Device.");
+                }
+
+                @Override
+                public void onUpdated(@NonNull NearbyDevice device) {}
+
+                @Override
+                public void onLost(@NonNull NearbyDevice device) {}
+            };
+
     private final BroadcastReceiver mScreenBroadcastReceiver =
             new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
+                    NearbyManager manager = getNearbyManager();
+                    if (manager == null) {
+                        Log.e(TAG, "Nearby Manager is null");
+                        return;
+                    }
                     if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-                        // TODO(b/221082271): removed this faked data once hooked with API codes.
-                        Log.d(TAG, "Update Presence CHRE filter");
-                        ByteString mac_addr = ByteString.copyFrom(new byte[] {1, 2, 3, 4, 5, 6});
-                        Blefilter.BleFilter filter =
-                                Blefilter.BleFilter.newBuilder()
-                                        .setId(0)
-                                        .setUuid(0xFCF1)
-                                        .setIntent(1)
-                                        .setMacAddress(mac_addr)
+                        Log.d(TAG, "Start CHRE scan.");
+                        byte[] secreteId = {1, 0, 0, 0};
+                        byte[] authenticityKey = {2, 0, 0, 0};
+                        byte[] publicKey = {3, 0, 0, 0};
+                        byte[] encryptedMetaData = {4, 0, 0, 0};
+                        byte[] encryptedMetaDataTag = {5, 0, 0, 0};
+                        PublicCredential publicCredential =
+                                new PublicCredential.Builder(
+                                                secreteId,
+                                                authenticityKey,
+                                                publicKey,
+                                                encryptedMetaData,
+                                                encryptedMetaDataTag)
                                         .build();
-                        Blefilter.BleFilters filters =
-                                Blefilter.BleFilters.newBuilder().addFilter(filter).build();
-                        updateFilters(filters);
+                        PresenceScanFilter presenceScanFilter =
+                                new PresenceScanFilter.Builder()
+                                        .setMaxPathLoss(3)
+                                        .addCredential(publicCredential)
+                                        .addPresenceAction(1)
+                                        .build();
+                        ScanRequest scanRequest =
+                                new ScanRequest.Builder()
+                                        .setScanType(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE)
+                                        .addScanFilter(presenceScanFilter)
+                                        .build();
+                        Log.i(
+                                TAG,
+                                String.format(
+                                        Locale.getDefault(),
+                                        "[PresenceManager] Start Presence scan with request: %s",
+                                        scanRequest.toString()));
+                        manager.startScan(
+                                scanRequest, Executors.newSingleThreadExecutor(), mScanCallback);
+                    } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                        Log.d(TAG, "Stop CHRE scan.");
+                        manager.stopScan(mScanCallback);
                     }
                 }
             };
 
-    public PresenceManager(Context context, PresenceCallback presenceCallback) {
-        mContext = context;
-        mPresenceCallback = presenceCallback;
-        mChreCallback = new ChreCallback();
+    public PresenceManager(LocatorContextWrapper contextWrapper) {
+        mLocatorContextWrapper = contextWrapper;
+        mLocator = mLocatorContextWrapper.getLocator();
         mIntentFilter = new IntentFilter();
     }
 
+    /** Null when the Nearby Service is not available. */
+    @Nullable
+    private NearbyManager getNearbyManager() {
+        return (NearbyManager)
+                mLocatorContextWrapper
+                        .getApplicationContext()
+                        .getSystemService(Context.NEARBY_SERVICE);
+    }
+
     /** Function called when nearby service start. */
-    public void initiate(ChreCommunication chreCommunication) {
-        mChreCommunication = chreCommunication;
-        mChreCommunication.start(mChreCallback, Collections.singleton(NANOAPP_ID));
-
+    public void initiate() {
         mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
-        mContext.registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
-    }
-
-    /** Updates the fitlers in Context Hub. */
-    public synchronized void updateFilters(Blefilter.BleFilters filters) {
-        mFilters = filters;
-        if (mChreStarted) {
-            sendFilters(mFilters);
-            mFilters = null;
-        }
-    }
-
-    private void sendFilters(Blefilter.BleFilters filters) {
-        NanoAppMessage message =
-                NanoAppMessage.createMessageToNanoApp(
-                        NANOAPP_ID, NANOAPP_MESSAGE_TYPE_FILTER, filters.toByteArray());
-        if (!mChreCommunication.sendMessageToNanoApp(message)) {
-            Log.e(TAG, "Failed to send filters to CHRE.");
-        }
-    }
-
-    private class ChreCallback implements ChreCommunication.ContextHubCommsCallback {
-
-        @Override
-        public void started(boolean success) {
-            if (success) {
-                synchronized (PresenceManager.this) {
-                    Log.i(TAG, "CHRE communication started");
-                    mChreStarted = true;
-                    if (mFilters != null) {
-                        sendFilters(mFilters);
-                        mFilters = null;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onHubReset() {
-            // TODO(b/221082271): hooked with upper level codes.
-            Log.i(TAG, "CHRE reset.");
-        }
-
-        @Override
-        public void onNanoAppRestart(long nanoAppId) {
-            // TODO(b/221082271): hooked with upper level codes.
-            Log.i(TAG, String.format("CHRE NanoApp %d restart.", nanoAppId));
-        }
-
-        @Override
-        public void onMessageFromNanoApp(NanoAppMessage message) {
-            if (message.getNanoAppId() != NANOAPP_ID) {
-                Log.e(TAG, "Received message from unknown nano app.");
-                return;
-            }
-            if (message.getMessageType() == NANOAPP_MESSAGE_TYPE_FILTER_RESULT) {
-                try {
-                    Blefilter.BleFilterResults results =
-                            Blefilter.BleFilterResults.parseFrom(message.getMessageBody());
-                    mPresenceCallback.onFilterResults(results);
-                } catch (InvalidProtocolBufferException e) {
-                    Log.e(
-                            TAG,
-                            String.format("Failed to decode the filter result %s", e.toString()));
-                }
-            }
-        }
+        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mLocatorContextWrapper
+                .getContext()
+                .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
index 7cc859c..f136695 100644
--- a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
@@ -20,11 +20,13 @@
 
 import android.content.Context;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanFilter;
 import android.nearby.ScanRequest;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -38,10 +40,9 @@
     protected final DiscoveryProviderController mController;
     protected final Executor mExecutor;
     protected Listener mListener;
+    protected List<ScanFilter> mScanFilters;
 
-    /**
-     * Interface for listening to discovery providers.
-     */
+    /** Interface for listening to discovery providers. */
     public interface Listener {
         /**
          * Called when a provider has a new nearby device available. May be invoked from any thread.
@@ -60,20 +61,20 @@
      * can now be expected. Always implies that the provider request is set to the empty request.
      * Always invoked on the provider executor.
      */
-    protected void onStart() { }
+    protected void onStart() {}
 
     /**
      * Callback invoked when the provider is stopped, and signals that no further callback
      * invocations will occur (until a further call to {@link #onStart()}. Always invoked on the
      * provider executor.
      */
-    protected void onStop() { }
+    protected void onStop() {}
 
     /**
      * Callback invoked to inform the provider of a new provider request which replaces any prior
      * provider request. Always invoked on the provider executor.
      */
-    protected void invalidateScanMode() { }
+    protected void invalidateScanMode() {}
 
     /**
      * Retrieves the controller for this discovery provider. Should never be invoked by subclasses,
@@ -134,5 +135,10 @@
         public int getProviderScanMode() {
             return mScanMode;
         }
+
+        @Override
+        public void setProviderScanFilters(List<ScanFilter> filters) {
+            mScanFilters = filters;
+        }
     }
 }
diff --git a/nearby/service/java/com/android/server/nearby/presence/ChreCommunication.java b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
similarity index 91%
rename from nearby/service/java/com/android/server/nearby/presence/ChreCommunication.java
rename to nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
index fc9863e..607c579 100644
--- a/nearby/service/java/com/android/server/nearby/presence/ChreCommunication.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.nearby.presence;
+package com.android.server.nearby.provider;
+
+import static com.android.server.nearby.NearbyService.TAG;
 
 import android.annotation.Nullable;
 import android.hardware.location.ContextHubClient;
@@ -60,8 +62,7 @@
         void onMessageFromNanoApp(NanoAppMessage message);
     }
 
-    public static final String TAG = "PresenceService";
-    @Nullable private final ContextHubManagerAdapter mManager;
+    private final Injector mInjector;
     private final Executor mExecutor;
 
     private boolean mStarted = false;
@@ -69,10 +70,14 @@
     @Nullable private ContextHubClient mContextHubClient;
 
     public ChreCommunication(Injector injector, Executor executor) {
-        this.mManager = injector.getContextHubManagerAdapter();
+        mInjector = injector;
         mExecutor = executor;
     }
 
+    public boolean available() {
+        return mInjector.getContextHubManagerAdapter() != null;
+    }
+
     /**
      * Starts communication with the contexthub. This will invoke {@link
      * ContextHubCommsCallback#start(boolean)} on completion.
@@ -81,18 +86,17 @@
      *     contexthub.
      */
     public synchronized void start(ContextHubCommsCallback callback, Set<Long> nanoAppIds) {
-        if (this.mManager == null) {
+        ContextHubManagerAdapter manager = mInjector.getContextHubManagerAdapter();
+        if (manager == null) {
             Log.e(TAG, "ContexHub not available in this device");
             return;
         } else {
             Log.i(TAG, "Start ChreCommunication");
         }
         Preconditions.checkNotNull(callback);
-        if (nanoAppIds.isEmpty() || mManager == null) {
-            callback.started(false);
-            return;
-        }
+        Preconditions.checkArgument(!nanoAppIds.isEmpty());
         if (mStarted) {
+            Log.i(TAG, "ChreCommunication already started");
             this.mCallback.started(true);
             return;
         }
@@ -102,17 +106,18 @@
         mStarted = true;
         this.mCallback = callback;
 
-        List<ContextHubInfo> contextHubs = mManager.getContextHubs();
+        List<ContextHubInfo> contextHubs = manager.getContextHubs();
 
         // Make a copy of the list so we can modify it during our async callbacks (in case the code
         // is still iterating)
         List<ContextHubInfo> validContextHubs = new ArrayList<>(contextHubs);
 
         for (ContextHubInfo info : contextHubs) {
-            ContextHubTransaction<List<NanoAppState>> transaction = mManager.queryNanoApps(info);
+            ContextHubTransaction<List<NanoAppState>> transaction = manager.queryNanoApps(info);
             Log.i(TAG, "After query Nano Apps ");
             transaction.setOnCompleteListener(
-                    new OnQueryCompleteListener(info, validContextHubs, nanoAppIds), mExecutor);
+                    new OnQueryCompleteListener(info, validContextHubs, nanoAppIds, manager),
+                    mExecutor);
         }
     }
 
@@ -202,14 +207,17 @@
         private final ContextHubInfo mQueriedContextHub;
         private final List<ContextHubInfo> mContextHubs;
         private final Set<Long> mNanoAppIds;
+        private final ContextHubManagerAdapter mManager;
 
         OnQueryCompleteListener(
                 ContextHubInfo queriedContextHub,
                 List<ContextHubInfo> contextHubs,
-                Set<Long> nanoAppIds) {
+                Set<Long> nanoAppIds,
+                ContextHubManagerAdapter manager) {
             this.mQueriedContextHub = queriedContextHub;
             this.mContextHubs = contextHubs;
             this.mNanoAppIds = nanoAppIds;
+            this.mManager = manager;
         }
 
         @Override
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
new file mode 100644
index 0000000..a70ef13
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.provider;
+
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.content.Context;
+import android.hardware.location.NanoAppMessage;
+import android.nearby.NearbyDevice;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanFilter;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import service.proto.Blefilter;
+
+import java.util.Collections;
+import java.util.concurrent.Executor;
+
+/** Discovery provider that uses CHRE Nearby Nanoapp to do scanning. */
+public class ChreDiscoveryProvider extends AbstractDiscoveryProvider {
+    // Nanoapp ID reserved for Nearby Presence.
+    /** @hide */
+    @VisibleForTesting public static final long NANOAPP_ID = 0x476f6f676c001031L;
+    /** @hide */
+    @VisibleForTesting public static final int NANOAPP_MESSAGE_TYPE_FILTER = 3;
+    /** @hide */
+    @VisibleForTesting public static final int NANOAPP_MESSAGE_TYPE_FILTER_RESULT = 4;
+
+    private static final int PRESENCE_UUID = 0xFCF1;
+
+    private ChreCommunication mChreCommunication;
+    private ChreCallback mChreCallback;
+    private boolean mChreStarted = false;
+    private Blefilter.BleFilters mFilters = null;
+    private int mFilterId;
+
+    public ChreDiscoveryProvider(
+            Context context, ChreCommunication chreCommunication, Executor executor) {
+        super(context, executor);
+        mChreCommunication = chreCommunication;
+        mChreCallback = new ChreCallback();
+        mFilterId = 0;
+    }
+
+    @Override
+    protected void onStart() {
+        Log.d(TAG, "Start CHRE scan");
+        mChreCommunication.start(mChreCallback, Collections.singleton(NANOAPP_ID));
+        updateFilters();
+    }
+
+    @Override
+    protected void onStop() {
+        mChreStarted = false;
+        mChreCommunication.stop();
+    }
+
+    @Override
+    protected void invalidateScanMode() {
+        onStop();
+        onStart();
+    }
+
+    public boolean available() {
+        return mChreCommunication.available();
+    }
+
+    private synchronized void updateFilters() {
+        if (mScanFilters == null) {
+            Log.e(TAG, "ScanFilters not set.");
+            return;
+        }
+        Blefilter.BleFilters.Builder filtersBuilder = Blefilter.BleFilters.newBuilder();
+        for (ScanFilter scanFilter : mScanFilters) {
+            PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
+            Blefilter.BleFilter filter =
+                    Blefilter.BleFilter.newBuilder()
+                            .setId(mFilterId)
+                            .setUuid(PRESENCE_UUID)
+                            .setIntent(presenceScanFilter.getPresenceActions().get(0))
+                            .build();
+            filtersBuilder.addFilter(filter);
+            mFilterId++;
+        }
+        mFilters = filtersBuilder.build();
+        if (mChreStarted) {
+            sendFilters(mFilters);
+            mFilters = null;
+        }
+    }
+
+    private void sendFilters(Blefilter.BleFilters filters) {
+        NanoAppMessage message =
+                NanoAppMessage.createMessageToNanoApp(
+                        NANOAPP_ID, NANOAPP_MESSAGE_TYPE_FILTER, filters.toByteArray());
+        if (!mChreCommunication.sendMessageToNanoApp(message)) {
+            Log.e(TAG, "Failed to send filters to CHRE.");
+        }
+    }
+
+    private class ChreCallback implements ChreCommunication.ContextHubCommsCallback {
+
+        @Override
+        public void started(boolean success) {
+            if (success) {
+                synchronized (ChreDiscoveryProvider.this) {
+                    Log.i(TAG, "CHRE communication started");
+                    mChreStarted = true;
+                    if (mFilters != null) {
+                        sendFilters(mFilters);
+                        mFilters = null;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onHubReset() {
+            // TODO(b/221082271): hooked with upper level codes.
+            Log.i(TAG, "CHRE reset.");
+        }
+
+        @Override
+        public void onNanoAppRestart(long nanoAppId) {
+            // TODO(b/221082271): hooked with upper level codes.
+            Log.i(TAG, String.format("CHRE NanoApp %d restart.", nanoAppId));
+        }
+
+        @Override
+        public void onMessageFromNanoApp(NanoAppMessage message) {
+            if (message.getNanoAppId() != NANOAPP_ID) {
+                Log.e(TAG, "Received message from unknown nano app.");
+                return;
+            }
+            if (mListener == null) {
+                Log.e(TAG, "the listener is not set in ChreDiscoveryProvider.");
+                return;
+            }
+            if (message.getMessageType() == NANOAPP_MESSAGE_TYPE_FILTER_RESULT) {
+                try {
+                    Blefilter.BleFilterResults results =
+                            Blefilter.BleFilterResults.parseFrom(message.getMessageBody());
+                    for (Blefilter.BleFilterResult filterResult : results.getResultList()) {
+                        Blefilter.PublicCredential credential = filterResult.getPublicCredential();
+                        PublicCredential publicCredential =
+                                new PublicCredential.Builder(
+                                                credential.getSecretId().toByteArray(),
+                                                credential.getAuthenticityKey().toByteArray(),
+                                                credential.getPublicKey().toByteArray(),
+                                                credential.getEncryptedMetadata().toByteArray(),
+                                                credential.getEncryptedMetadataTag().toByteArray())
+                                        .build();
+                        NearbyDeviceParcelable device =
+                                new NearbyDeviceParcelable.Builder()
+                                        .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+                                        .setMedium(NearbyDevice.Medium.BLE)
+                                        .setTxPower(filterResult.getTxPower())
+                                        .setRssi(filterResult.getRssi())
+                                        .setAction(filterResult.getIntent())
+                                        .setPublicCredential(publicCredential)
+                                        .build();
+                        mExecutor.execute(() -> mListener.onNearbyDeviceDiscovered(device));
+                    }
+                } catch (InvalidProtocolBufferException e) {
+                    Log.e(
+                            TAG,
+                            String.format("Failed to decode the filter result %s", e.toString()));
+                }
+            }
+        }
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java
index 469f623..fa1a874 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java
@@ -17,42 +17,43 @@
 package com.android.server.nearby.provider;
 
 import android.annotation.Nullable;
+import android.nearby.ScanFilter;
 import android.nearby.ScanRequest;
 
-/**
- * Interface for controlling discovery providers.
- */
+import java.util.List;
+
+/** Interface for controlling discovery providers. */
 interface DiscoveryProviderController {
 
     /**
-     * Sets the listener which can expect to receive all state updates from after this point.
-     * May be invoked at any time.
+     * Sets the listener which can expect to receive all state updates from after this point. May be
+     * invoked at any time.
      */
     void setListener(@Nullable AbstractDiscoveryProvider.Listener listener);
 
-    /**
-     * Returns true if in the started state.
-     */
+    /** Returns true if in the started state. */
     boolean isStarted();
 
     /**
-     * Starts the discovery provider. Must be invoked before any other method (except
-     * {@link #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}).
+     * Starts the discovery provider. Must be invoked before any other method (except {@link
+     * #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}).
      */
     void start();
 
     /**
      * Stops the discovery provider. No other methods may be invoked after this method (except
-     * {@link #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}), until {@link #start()} is called again.
+     * {@link #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}), until {@link #start()}
+     * is called again.
      */
     void stop();
 
-    /**
-     * Sets the desired scan mode.
-     */
+    /** Sets the desired scan mode. */
     void setProviderScanMode(@ScanRequest.ScanMode int scanMode);
 
     /** Gets the controller scan mode. */
     @ScanRequest.ScanMode
     int getProviderScanMode();
+
+    /** Sets the scan filters. */
+    void setProviderScanFilters(List<ScanFilter> filters);
 }
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 7ff3110..af1c0b5 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -20,6 +20,7 @@
 
 import static com.android.server.nearby.NearbyService.TAG;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.nearby.IScanListener;
 import android.nearby.NearbyDeviceParcelable;
@@ -35,20 +36,22 @@
 import com.android.server.nearby.metrics.NearbyMetrics;
 import com.android.server.nearby.presence.PresenceDiscoveryResult;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
-/**
- * Manages all aspects of discovery providers.
- */
+/** Manages all aspects of discovery providers. */
 public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener {
 
     protected final Object mLock = new Object();
     private final Context mContext;
     private final BleDiscoveryProvider mBleDiscoveryProvider;
+    @Nullable private final ChreDiscoveryProvider mChreDiscoveryProvider;
     private @ScanRequest.ScanMode int mScanMode;
 
     @GuardedBy("mLock")
@@ -65,18 +68,24 @@
                 }
                 if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
                     List<ScanFilter> presenceFilters =
-                            record.getScanRequest().getScanFilters().stream().filter(
-                                    scanFilter -> scanFilter.getType()
-                                            == SCAN_TYPE_NEARBY_PRESENCE).collect(
-                                    Collectors.toList());
+                            record.getScanRequest().getScanFilters().stream()
+                                    .filter(
+                                            scanFilter ->
+                                                    scanFilter.getType()
+                                                            == SCAN_TYPE_NEARBY_PRESENCE)
+                                    .collect(Collectors.toList());
+                    Log.i(
+                            TAG,
+                            String.format("match with filters size: %d", presenceFilters.size()));
                     if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
                         continue;
                     }
                 }
                 try {
-                    record.getScanListener().onDiscovered(
-                            PrivacyFilter.filter(record.getScanRequest().getScanType(),
-                                    nearbyDevice));
+                    record.getScanListener()
+                            .onDiscovered(
+                                    PrivacyFilter.filter(
+                                            record.getScanRequest().getScanType(), nearbyDevice));
                     NearbyMetrics.logScanDeviceDiscovered(
                             record.hashCode(), record.getScanRequest(), nearbyDevice);
                 } catch (RemoteException e) {
@@ -89,6 +98,10 @@
     public DiscoveryProviderManager(Context context, Injector injector) {
         mContext = context;
         mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector);
+        Executor executor = Executors.newSingleThreadExecutor();
+        mChreDiscoveryProvider =
+                new ChreDiscoveryProvider(
+                        mContext, new ChreCommunication(injector, executor), executor);
         mScanTypeScanListenerRecordMap = new HashMap<>();
     }
 
@@ -96,23 +109,24 @@
      * Registers the listener in the manager and starts scan according to the requested scan mode.
      */
     public boolean registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+        Log.i(TAG, "DiscoveryProviderManager registerScanListener");
         synchronized (mLock) {
             IBinder listenerBinder = listener.asBinder();
             if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
-                ScanRequest savedScanRequest = mScanTypeScanListenerRecordMap
-                        .get(listenerBinder).getScanRequest();
+                ScanRequest savedScanRequest =
+                        mScanTypeScanListenerRecordMap.get(listenerBinder).getScanRequest();
                 if (scanRequest.equals(savedScanRequest)) {
                     Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
                     return true;
                 }
             }
+            ScanListenerRecord scanListenerRecord = new ScanListenerRecord(scanRequest, listener);
+            mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
 
             if (!startProviders(scanRequest)) {
                 return false;
             }
 
-            ScanListenerRecord scanListenerRecord = new ScanListenerRecord(scanRequest, listener);
-            mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
             NearbyMetrics.logScanStarted(scanListenerRecord.hashCode(), scanRequest);
             if (mScanMode < scanRequest.getScanMode()) {
                 mScanMode = scanRequest.getScanMode();
@@ -129,21 +143,23 @@
         IBinder listenerBinder = listener.asBinder();
         synchronized (mLock) {
             if (!mScanTypeScanListenerRecordMap.containsKey(listenerBinder)) {
-                Log.w(TAG,
+                Log.w(
+                        TAG,
                         "Cannot unregister the scanRequest because the request is never "
                                 + "registered.");
                 return;
             }
 
-            ScanListenerRecord removedRecord = mScanTypeScanListenerRecordMap
-                    .remove(listenerBinder);
-            NearbyMetrics.logScanStopped(
-                    removedRecord.hashCode(), removedRecord.getScanRequest());
+            ScanListenerRecord removedRecord =
+                    mScanTypeScanListenerRecordMap.remove(listenerBinder);
+            NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest());
             if (mScanTypeScanListenerRecordMap.isEmpty()) {
                 stopProviders();
                 return;
             }
 
+            // TODO(b/221082271): updates the scan with reduced filters.
+
             // Removes current highest scan mode requested and sets the next highest scan mode.
             if (removedRecord.getScanRequest().getScanMode() == mScanMode) {
                 @ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER;
@@ -166,8 +182,13 @@
     // starts successfully.
     private boolean startProviders(ScanRequest scanRequest) {
         if (scanRequest.isBleEnabled()) {
-            startBleProvider(scanRequest);
-            return true;
+            if (mChreDiscoveryProvider.available()) {
+                startChreProvider();
+                return true;
+            } else {
+                startBleProvider(scanRequest);
+                return true;
+            }
         }
         return false;
     }
@@ -181,31 +202,57 @@
         }
     }
 
+    private void startChreProvider() {
+        Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
+        synchronized (mLock) {
+            mChreDiscoveryProvider.getController().setListener(this);
+            List<ScanFilter> scanFilters = new ArrayList();
+            for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
+                ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
+                List<ScanFilter> presenceFilters =
+                        record.getScanRequest().getScanFilters().stream()
+                                .filter(
+                                        scanFilter ->
+                                                scanFilter.getType() == SCAN_TYPE_NEARBY_PRESENCE)
+                                .collect(Collectors.toList());
+                scanFilters.addAll(presenceFilters);
+            }
+            mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
+            mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+            mChreDiscoveryProvider.getController().start();
+        }
+    }
+
     private void stopProviders() {
         stopBleProvider();
+        stopChreProvider();
     }
 
     private void stopBleProvider() {
         mBleDiscoveryProvider.getController().stop();
     }
 
-    private void invalidateProviderScanMode() {
-        if (!mBleDiscoveryProvider.getController().isStarted()) {
-            Log.d(TAG,
-                    "Skip invalidating BleDiscoveryProvider scan mode because the provider not "
-                            + "started.");
-            return;
-        }
-        mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+    private void stopChreProvider() {
+        mChreDiscoveryProvider.getController().stop();
     }
 
-    private static boolean presenceFilterMatches(NearbyDeviceParcelable device,
-            List<ScanFilter> scanFilters) {
+    private void invalidateProviderScanMode() {
+        if (mBleDiscoveryProvider.getController().isStarted()) {
+            mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+        } else {
+            Log.d(
+                    TAG,
+                    "Skip invalidating BleDiscoveryProvider scan mode because the provider not "
+                            + "started.");
+        }
+    }
+
+    private static boolean presenceFilterMatches(
+            NearbyDeviceParcelable device, List<ScanFilter> scanFilters) {
         if (scanFilters.isEmpty()) {
             return true;
         }
-        PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromScanData(
-                device.getData(), device.getRssi());
+        PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromDevice(device);
         for (ScanFilter scanFilter : scanFilters) {
             PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
             if (discoveryResult.matches(presenceScanFilter)) {
@@ -221,7 +268,6 @@
 
         private final IScanListener mScanListener;
 
-
         ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener) {
             mScanListener = iScanListener;
             mScanRequest = scanRequest;
diff --git a/nearby/service/proto/src/presence/blefilter.proto b/nearby/service/proto/src/presence/blefilter.proto
index da56522..9f75d34 100644
--- a/nearby/service/proto/src/presence/blefilter.proto
+++ b/nearby/service/proto/src/presence/blefilter.proto
@@ -47,6 +47,14 @@
   optional bytes metadata_encryption_key_tag = 2;
 }
 
+message PublicCredential {
+  optional bytes secret_id = 1;
+  optional bytes authenticity_key = 2;
+  optional bytes public_key = 3;
+  optional bytes encrypted_metadata = 4;
+  optional bytes encrypted_metadata_tag = 5;
+}
+
 message BleFilter {
   optional uint32 id = 1;  // Required, unique id of this filter.
   // Maximum delay to notify the client after an event occurs.
@@ -73,8 +81,11 @@
 // FilterResult is returned to host when a BLE event matches a Filter.
 message BleFilterResult {
   optional uint32 id = 1;  // id of the matched Filter.
-  // TODO(b/193756395): replace with BLE event proto.
-  optional bytes raw_data = 2;
+  optional uint32 tx_power = 2;
+  optional uint32 rssi = 3;
+  optional uint32 intent = 4;
+  optional bytes bluetooth_address = 5;
+  optional PublicCredential public_credential = 6;
 }
 
 message BleFilterResults {
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 87fc4cd..b9ab95f 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
@@ -20,6 +20,7 @@
 
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PublicCredential;
 import android.os.Build;
 import android.os.Parcel;
 
@@ -46,37 +47,50 @@
 
     @Before
     public void setUp() {
-        mBuilder =  new NearbyDeviceParcelable.Builder()
-                .setName("testDevice")
-                .setMedium(NearbyDevice.Medium.BLE)
-                .setRssi(RSSI)
-                .setTxPower(-90)
-                .setFastPairModelId(FAST_PAIR_MODEL_ID)
-                .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                .setData(SCAN_DATA);
+        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")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testToString() {
+        PublicCredential publicCredential =
+                new PublicCredential.Builder(
+                                new byte[] {1},
+                                new byte[] {2},
+                                new byte[] {3},
+                                new byte[] {4},
+                                new byte[] {5})
+                        .build();
         NearbyDeviceParcelable nearbyDeviceParcelable =
-                mBuilder.setFastPairModelId(null).setData(null).build();
+                mBuilder.setFastPairModelId(null)
+                        .setData(null)
+                        .setPublicCredential(publicCredential)
+                        .build();
 
-        assertThat(nearbyDeviceParcelable.toString()).isEqualTo(
-                "NearbyDeviceParcelable[name=testDevice, medium=BLE, rssi=-60, txPower=-90, "
-                        + "bluetoothAddress="
-                        + BLUETOOTH_ADDRESS + ", fastPairModelId=null, data=null]");
+        assertThat(nearbyDeviceParcelable.toString())
+                .isEqualTo(
+                        "NearbyDeviceParcelable[name=testDevice, medium=BLE, txPower=0, rssi=-60,"
+                                + " action=0, bluetoothAddress="
+                                + BLUETOOTH_ADDRESS
+                                + ", fastPairModelId=null, data=null]");
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void test_defaultNullFields() {
-        NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder()
-                .setMedium(NearbyDevice.Medium.BLE)
-                .setRssi(RSSI)
-                .build();
+        NearbyDeviceParcelable nearbyDeviceParcelable =
+                new NearbyDeviceParcelable.Builder()
+                        .setMedium(NearbyDevice.Medium.BLE)
+                        .setRssi(RSSI)
+                        .build();
 
         assertThat(nearbyDeviceParcelable.getName()).isNull();
         assertThat(nearbyDeviceParcelable.getFastPairModelId()).isNull();
@@ -88,15 +102,15 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testWriteParcel() {
-        NearbyDeviceParcelable nearbyDeviceParcelable =  mBuilder.build();
+        NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.build();
 
         Parcel parcel = Parcel.obtain();
         nearbyDeviceParcelable.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
-                parcel);
+        NearbyDeviceParcelable actualNearbyDevice =
+                NearbyDeviceParcelable.CREATOR.createFromParcel(parcel);
         parcel.recycle();
 
         assertThat(actualNearbyDevice.getRssi()).isEqualTo(RSSI);
@@ -106,32 +120,30 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testWriteParcel_nullModelId() {
-        NearbyDeviceParcelable nearbyDeviceParcelable =
-                mBuilder.setFastPairModelId(null).build();
+        NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setFastPairModelId(null).build();
 
         Parcel parcel = Parcel.obtain();
         nearbyDeviceParcelable.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
-                parcel);
+        NearbyDeviceParcelable actualNearbyDevice =
+                NearbyDeviceParcelable.CREATOR.createFromParcel(parcel);
         parcel.recycle();
 
         assertThat(actualNearbyDevice.getFastPairModelId()).isNull();
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testWriteParcel_nullBluetoothAddress() {
-        NearbyDeviceParcelable nearbyDeviceParcelable =
-                mBuilder.setBluetoothAddress(null).build();
+        NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setBluetoothAddress(null).build();
 
         Parcel parcel = Parcel.obtain();
         nearbyDeviceParcelable.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        NearbyDeviceParcelable actualNearbyDevice = NearbyDeviceParcelable.CREATOR.createFromParcel(
-                parcel);
+        NearbyDeviceParcelable actualNearbyDevice =
+                NearbyDeviceParcelable.CREATOR.createFromParcel(parcel);
         parcel.recycle();
 
         assertThat(actualNearbyDevice.getBluetoothAddress()).isNull();
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
index 10b3cf2..f05f65f 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
@@ -60,7 +60,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testBuilder() {
         PrivateCredential credential = mBuilder.build();
 
@@ -77,7 +77,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testWriteParcel() {
         PrivateCredential credential = mBuilder.build();
 
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
index b4a34cc..11bbacc 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
@@ -37,29 +37,32 @@
 
 import java.util.Arrays;
 
-/**
- * Tests for {@link PresenceCredential}.
- */
+/** 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 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 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, PUBLIC_KEY,
-                ENCRYPTED_METADATA, METADATA_ENCRYPTION_KEY_TAG)
-                .addCredentialElement(new CredentialElement(KEY, VALUE))
-                .setIdentityType(IDENTITY_TYPE_PRIVATE);
+        mBuilder =
+                new PublicCredential.Builder(
+                                SECRETE_ID,
+                                AUTHENTICITY_KEY,
+                                PUBLIC_KEY,
+                                ENCRYPTED_METADATA,
+                                METADATA_ENCRYPTION_KEY_TAG)
+                        .addCredentialElement(new CredentialElement(KEY, VALUE))
+                        .setIdentityType(IDENTITY_TYPE_PRIVATE);
     }
 
     @Test
@@ -74,8 +77,11 @@
         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();
+        assertThat(
+                        Arrays.equals(
+                                credential.getEncryptedMetadataKeyTag(),
+                                METADATA_ENCRYPTION_KEY_TAG))
+                .isTrue();
     }
 
     @Test
@@ -86,19 +92,73 @@
         Parcel parcel = Parcel.obtain();
         credential.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        PublicCredential credentialFromParcel = PublicCredential.CREATOR.createFromParcel(
-                parcel);
+        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.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();
+        assertThat(Arrays.equals(credentialFromParcel.getEncryptedMetadata(), ENCRYPTED_METADATA))
+                .isTrue();
+        assertThat(
+                        Arrays.equals(
+                                credentialFromParcel.getEncryptedMetadataKeyTag(),
+                                METADATA_ENCRYPTION_KEY_TAG))
+                .isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testEquals() {
+        PublicCredential credentialOne =
+                new PublicCredential.Builder(
+                                SECRETE_ID,
+                                AUTHENTICITY_KEY,
+                                PUBLIC_KEY,
+                                ENCRYPTED_METADATA,
+                                METADATA_ENCRYPTION_KEY_TAG)
+                        .addCredentialElement(new CredentialElement(KEY, VALUE))
+                        .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                        .build();
+
+        PublicCredential credentialTwo =
+                new PublicCredential.Builder(
+                                SECRETE_ID,
+                                AUTHENTICITY_KEY,
+                                PUBLIC_KEY,
+                                ENCRYPTED_METADATA,
+                                METADATA_ENCRYPTION_KEY_TAG)
+                        .addCredentialElement(new CredentialElement(KEY, VALUE))
+                        .setIdentityType(IDENTITY_TYPE_PRIVATE)
+                        .build();
+        assertThat(credentialOne.equals((Object) credentialTwo)).isTrue();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testUnEquals() {
+        byte[] idOne = new byte[] {1, 2, 3, 4};
+        byte[] idTwo = new byte[] {4, 5, 6, 7};
+        PublicCredential credentialOne =
+                new PublicCredential.Builder(
+                                idOne,
+                                AUTHENTICITY_KEY,
+                                PUBLIC_KEY,
+                                ENCRYPTED_METADATA,
+                                METADATA_ENCRYPTION_KEY_TAG)
+                        .build();
+
+        PublicCredential credentialTwo =
+                new PublicCredential.Builder(
+                                idTwo,
+                                AUTHENTICITY_KEY,
+                                PUBLIC_KEY,
+                                ENCRYPTED_METADATA,
+                                METADATA_ENCRYPTION_KEY_TAG)
+                        .build();
+        assertThat(credentialOne.equals((Object) credentialTwo)).isFalse();
     }
 }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
index b591ae0..2ab6dbd 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
@@ -64,9 +64,6 @@
                 .setRemoveAllDevicesDuringPairing(true)
                 .build()
         )
-
-        // TODO(b/222070055): Workaround the FATAL EXCEPTION after the end of initial pairing.
-        simulator!!.setSuppressSubsequentPairingNotification(true)
     }
 
     fun getProviderSimulatorBleAddress() = simulator!!.bleAddress!!
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
index 80ca855..be94031 100644
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
@@ -21,12 +21,12 @@
 import android.nearby.FastPairDeviceMetadata
 import android.nearby.FastPairDiscoveryItem
 import com.google.common.io.BaseEncoding
-import com.google.gson.Gson
+import com.google.gson.GsonBuilder
 import com.google.gson.annotations.SerializedName
 
 /** Manage a cache of Fast Pair test data for testing. */
 class FastPairTestDataCache {
-    private val gson = Gson()
+    private val gson = GsonBuilder().disableHtmlEscaping().create()
     private val accountKeyDeviceMetadataList = mutableListOf<FastPairAccountKeyDeviceMetadata>()
     private val antispoofKeyDeviceMetadataDataMap =
         mutableMapOf<String, FastPairAntispoofKeyDeviceMetadataData>()
@@ -71,13 +71,13 @@
         @SerializedName("account_key") val accountKey: String?,
         @SerializedName("sha256_account_key_public_address") val accountKeyPublicAddress: String?,
         @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?,
-        @SerializedName("fast_pair_discovery_item") val discoveryItem: FastPairDiscoveryItemData?,
+        @SerializedName("fast_pair_discovery_item") val discoveryItem: FastPairDiscoveryItemData?
     ) {
         constructor(meta: FastPairAccountKeyDeviceMetadata) : this(
             accountKey = meta.deviceAccountKey?.base64Encode(),
             accountKeyPublicAddress = meta.sha256DeviceAccountKeyPublicAddress?.base64Encode(),
             deviceMeta = meta.fastPairDeviceMetadata?.let { FastPairDeviceMetadataData(it) },
-            discoveryItem = meta.fastPairDiscoveryItem?.let { FastPairDiscoveryItemData(it) },
+            discoveryItem = meta.fastPairDiscoveryItem?.let { FastPairDiscoveryItemData(it) }
         )
 
         fun toFastPairAccountKeyDeviceMetadata(): FastPairAccountKeyDeviceMetadata {
@@ -92,7 +92,7 @@
 
     data class FastPairAntispoofKeyDeviceMetadataData(
         @SerializedName("anti_spoofing_public_key_str") val antispoofPublicKey: String?,
-        @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?,
+        @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?
     ) {
         fun toFastPairAntispoofKeyDeviceMetadata(): FastPairAntispoofKeyDeviceMetadata {
             return FastPairAntispoofKeyDeviceMetadata.Builder()
@@ -135,7 +135,7 @@
         @SerializedName("unable_to_connect_description") val unableToConnectDescription: String?,
         @SerializedName("unable_to_connect_title") val unableToConnectTitle: String?,
         @SerializedName("update_companion_app_description") val updateCompAppDes: String?,
-        @SerializedName("wait_launch_companion_app_description") val waitLaunchCompApp: String?,
+        @SerializedName("wait_launch_companion_app_description") val waitLaunchCompApp: String?
     ) {
         constructor(meta: FastPairDeviceMetadata) : this(
             assistantSetupHalfSheet = meta.assistantSetupHalfSheet,
@@ -170,7 +170,7 @@
             unableToConnectDescription = meta.unableToConnectDescription,
             unableToConnectTitle = meta.unableToConnectTitle,
             updateCompAppDes = meta.updateCompanionAppDescription,
-            waitLaunchCompApp = meta.waitLaunchCompanionAppDescription,
+            waitLaunchCompApp = meta.waitLaunchCompanionAppDescription
         )
 
         fun toFastPairDeviceMetadata(): FastPairDeviceMetadata {
@@ -242,7 +242,7 @@
         @SerializedName("title") val title: String?,
         @SerializedName("trigger_id") val triggerId: String?,
         @SerializedName("tx_power") val txPower: Int,
-        @SerializedName("type") val type: Int,
+        @SerializedName("type") val type: Int
     ) {
         constructor(item: FastPairDiscoveryItem) : this(
             actionUrl = item.actionUrl,
@@ -274,7 +274,7 @@
             title = item.title,
             triggerId = item.triggerId,
             txPower = item.txPower,
-            type = item.type,
+            type = item.type
         )
 
         fun toFastPairDiscoveryItem(): FastPairDiscoveryItem {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java b/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java
index 252bb90..c4d1ee2 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java
@@ -20,6 +20,7 @@
 import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_SHARE;
 
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PublicCredential;
 import android.nearby.ScanRequest;
 import android.os.WorkSource;
 
@@ -42,39 +43,50 @@
     private static final int RSSI = -60;
     private static final String FAST_PAIR_MODEL_ID = "1234";
     private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
-    private static final byte[] SCAN_DATA = new byte[]{1, 2, 3, 4};
+    private static final byte[] SCAN_DATA = new byte[] {1, 2, 3, 4};
+    private static final PublicCredential PUBLIC_CREDENTIAL =
+            new PublicCredential.Builder(
+                            new byte[] {1},
+                            new byte[] {2},
+                            new byte[] {3},
+                            new byte[] {4},
+                            new byte[] {5})
+                    .build();
 
     private final WorkSource mWorkSource = new WorkSource(WORK_SOURCE_UID);
     private final WorkSource mEmptyWorkSource = new WorkSource();
 
-    private final ScanRequest.Builder mScanRequestBuilder = new ScanRequest.Builder()
-            .setScanMode(SCAN_MODE_BALANCED)
-            .setScanType(SCAN_TYPE_NEARBY_SHARE);
-    private final ScanRequest mScanRequest = mScanRequestBuilder
-            .setWorkSource(mWorkSource)
-            .build();
-    private final ScanRequest mScanRequestWithEmptyWorkSource = mScanRequestBuilder
-            .setWorkSource(mEmptyWorkSource)
-            .build();
+    private final ScanRequest.Builder mScanRequestBuilder =
+            new ScanRequest.Builder()
+                    .setScanMode(SCAN_MODE_BALANCED)
+                    .setScanType(SCAN_TYPE_NEARBY_SHARE);
+    private final ScanRequest mScanRequest = mScanRequestBuilder.setWorkSource(mWorkSource).build();
+    private final ScanRequest mScanRequestWithEmptyWorkSource =
+            mScanRequestBuilder.setWorkSource(mEmptyWorkSource).build();
 
     private final NearbyDeviceParcelable mNearbyDevice =
             new NearbyDeviceParcelable.Builder()
                     .setName(DEVICE_NAME)
                     .setMedium(SCAN_MEDIUM)
+                    .setTxPower(1)
                     .setRssi(RSSI)
+                    .setAction(1)
+                    .setPublicCredential(PUBLIC_CREDENTIAL)
                     .setFastPairModelId(FAST_PAIR_MODEL_ID)
                     .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                    .setData(SCAN_DATA).build();
+                    .setData(SCAN_DATA)
+                    .build();
 
     private MockitoSession mSession;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mSession = ExtendedMockito.mockitoSession()
-                .strictness(Strictness.LENIENT)
-                .mockStatic(NearbyStatsLog.class)
-                .startMocking();
+        mSession =
+                ExtendedMockito.mockitoSession()
+                        .strictness(Strictness.LENIENT)
+                        .mockStatic(NearbyStatsLog.class)
+                        .startMocking();
     }
 
     @After
@@ -164,8 +176,8 @@
 
     @Test
     public void testLogScanDeviceDiscovered_emptyWorkSource() {
-        NearbyMetrics.logScanDeviceDiscovered(SESSION_ID, mScanRequestWithEmptyWorkSource,
-                mNearbyDevice);
+        NearbyMetrics.logScanDeviceDiscovered(
+                SESSION_ID, mScanRequestWithEmptyWorkSource, mNearbyDevice);
         ExtendedMockito.verify(() -> NearbyStatsLog.write(
                 NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
                 -1,
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
index f9676e3..d32e325 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
@@ -16,93 +16,24 @@
 
 package com.android.server.nearby.presence;
 
-import static com.android.server.nearby.presence.PresenceManager.NANOAPP_ID;
-import static com.android.server.nearby.presence.PresenceManager.NANOAPP_MESSAGE_TYPE_FILTER_RESULT;
 
-import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.location.NanoAppMessage;
 
 import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Collections;
-
-import service.proto.Blefilter;
-
 public class PresenceManagerTest {
-    @Mock private Context mContext;
-    @Mock private PresenceManager.PresenceCallback mPresenceCallback;
-    @Mock private ChreCommunication mChreCommunication;
-
-    @Captor ArgumentCaptor<ChreCommunication.ContextHubCommsCallback> mChreCallbackCaptor;
-    @Captor ArgumentCaptor<NanoAppMessage> mNanoAppMessageCaptor;
-
     private PresenceManager mPresenceManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mPresenceManager = new PresenceManager(mContext, mPresenceCallback);
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testInit() {
-        mPresenceManager.initiate(mChreCommunication);
-        verify(mChreCommunication).start(any(), eq(Collections.singleton(NANOAPP_ID)));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testOnFilterResults() {
-        Blefilter.BleFilterResults results = Blefilter.BleFilterResults.newBuilder().build();
-        NanoAppMessage chre_message =
-                NanoAppMessage.createMessageToNanoApp(
-                        NANOAPP_ID, NANOAPP_MESSAGE_TYPE_FILTER_RESULT, results.toByteArray());
-        mPresenceManager.initiate(mChreCommunication);
-        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
-        mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message);
-        verify(mPresenceCallback).onFilterResults(eq(results));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testUpdateFiltersBeforeChreStarted() {
-        Blefilter.BleFilters filters = Blefilter.BleFilters.newBuilder().build();
-        mPresenceManager.updateFilters(filters);
-        verify(mChreCommunication, never()).sendMessageToNanoApp(any());
-        mPresenceManager.initiate(mChreCommunication);
-        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
-        mChreCallbackCaptor.getValue().started(true);
-        verify(mChreCommunication, times(1)).sendMessageToNanoApp(mNanoAppMessageCaptor.capture());
-        assertThat(mNanoAppMessageCaptor.getValue().getMessageBody())
-                .isEqualTo(filters.toByteArray());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testUpdateFiltersAfterChreStarted() {
-        mPresenceManager.initiate(mChreCommunication);
-        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
-        mChreCallbackCaptor.getValue().started(true);
-        Blefilter.BleFilters filters = Blefilter.BleFilters.newBuilder().build();
-        mPresenceManager.updateFilters(filters);
-        verify(mChreCommunication, times(1)).sendMessageToNanoApp(mNanoAppMessageCaptor.capture());
-        assertThat(mNanoAppMessageCaptor.getValue().getMessageBody())
-                .isEqualTo(filters.toByteArray());
-    }
+    public void testInit() {}
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ChreCommunicationTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
similarity index 85%
rename from nearby/tests/unit/src/com/android/server/nearby/presence/ChreCommunicationTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
index b221f46..1b29b52 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/ChreCommunicationTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.nearby.presence;
+package com.android.server.nearby.provider;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -65,10 +65,11 @@
         when(mTransactionResponse.getContents())
                 .thenReturn(
                         Collections.singletonList(
-                                new NanoAppState(PresenceManager.NANOAPP_ID, 1, true)));
+                                new NanoAppState(ChreDiscoveryProvider.NANOAPP_ID, 1, true)));
 
         mChreCommunication = new ChreCommunication(mInjector, new InlineExecutor());
-        mChreCommunication.start(mChreCallback, Collections.singleton(PresenceManager.NANOAPP_ID));
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
 
         verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
         mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
@@ -89,8 +90,8 @@
     public void testSendMessageToNanApp() {
         NanoAppMessage message =
                 NanoAppMessage.createMessageToNanoApp(
-                        PresenceManager.NANOAPP_ID,
-                        PresenceManager.NANOAPP_MESSAGE_TYPE_FILTER,
+                        ChreDiscoveryProvider.NANOAPP_ID,
+                        ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER,
                         new byte[] {1, 2, 3});
         mChreCommunication.sendMessageToNanoApp(message);
         verify(mClient).sendMessageToNanoApp(eq(message));
@@ -100,8 +101,8 @@
     public void testOnMessageFromNanoApp() {
         NanoAppMessage message =
                 NanoAppMessage.createMessageToNanoApp(
-                        PresenceManager.NANOAPP_ID,
-                        PresenceManager.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
+                        ChreDiscoveryProvider.NANOAPP_ID,
+                        ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
                         new byte[] {1, 2, 3});
         mChreCommunication.onMessageFromNanoApp(mClient, message);
         verify(mChreCallback).onMessageFromNanoApp(eq(message));
@@ -115,8 +116,8 @@
 
     @Test
     public void testOnNanoAppLoaded() {
-        mChreCommunication.onNanoAppLoaded(mClient, PresenceManager.NANOAPP_ID);
-        verify(mChreCallback).onNanoAppRestart(eq(PresenceManager.NANOAPP_ID));
+        mChreCommunication.onNanoAppLoaded(mClient, ChreDiscoveryProvider.NANOAPP_ID);
+        verify(mChreCallback).onNanoAppRestart(eq(ChreDiscoveryProvider.NANOAPP_ID));
     }
 
     private static class InlineExecutor implements Executor {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
new file mode 100644
index 0000000..7c0dd92
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.location.NanoAppMessage;
+import android.nearby.ScanFilter;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Blefilter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class ChreDiscoveryProviderTest {
+    @Mock AbstractDiscoveryProvider.Listener mListener;
+    @Mock ChreCommunication mChreCommunication;
+
+    @Captor ArgumentCaptor<ChreCommunication.ContextHubCommsCallback> mChreCallbackCaptor;
+
+    private ChreDiscoveryProvider mChreDiscoveryProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mChreDiscoveryProvider =
+                new ChreDiscoveryProvider(context, mChreCommunication, new InLineExecutor());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testOnStart() {
+        List<ScanFilter> scanFilters = new ArrayList<>();
+        mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
+        mChreDiscoveryProvider.onStart();
+        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
+        mChreCallbackCaptor.getValue().started(true);
+        verify(mChreCommunication).sendMessageToNanoApp(any());
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testOnNearbyDeviceDiscovered() {
+        Blefilter.PublicCredential credential =
+                Blefilter.PublicCredential.newBuilder()
+                        .setSecretId(ByteString.copyFrom(new byte[] {1}))
+                        .setAuthenticityKey(ByteString.copyFrom(new byte[2]))
+                        .setPublicKey(ByteString.copyFrom(new byte[3]))
+                        .setEncryptedMetadata(ByteString.copyFrom(new byte[4]))
+                        .setEncryptedMetadataTag(ByteString.copyFrom(new byte[5]))
+                        .build();
+        Blefilter.BleFilterResult result =
+                Blefilter.BleFilterResult.newBuilder()
+                        .setTxPower(2)
+                        .setRssi(1)
+                        .setPublicCredential(credential)
+                        .build();
+        Blefilter.BleFilterResults results =
+                Blefilter.BleFilterResults.newBuilder().addResult(result).build();
+        NanoAppMessage chre_message =
+                NanoAppMessage.createMessageToNanoApp(
+                        ChreDiscoveryProvider.NANOAPP_ID,
+                        ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
+                        results.toByteArray());
+        mChreDiscoveryProvider.getController().setListener(mListener);
+        mChreDiscoveryProvider.onStart();
+        verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any());
+        mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message);
+        verify(mListener).onNearbyDeviceDiscovered(any());
+    }
+
+    private static class InLineExecutor implements Executor {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    }
+}