Start scan when boot up and also add range check

Test: unit test
Bug: 218491888
Ignore-AOSP-First: nearby_not_in_aosp_yet
Change-Id: I35140039899643912125d9248dcf249d691e1868
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java b/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
index 321e125..eec52ad 100644
--- a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
+++ b/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
@@ -23,12 +23,19 @@
  * square of the frequency of the propagation signal.
  */
 public final class RangingUtils {
-    /*
-     * Key to variable names used in this class (viz. Physics):
+    private static final int MAX_RSSI_VALUE = 126;
+    private static final int MIN_RSSI_VALUE = -127;
+
+    private RangingUtils() {
+    }
+
+    /* This was original derived in {@link com.google.android.gms.beacon.util.RangingUtils} from
+     * <a href="http://en.wikipedia.org/wiki/Free-space_path_loss">Free-space_path_loss</a>.
+     * Duplicated here for easy reference.
      *
      * c   = speed of light (2.9979 x 10^8 m/s);
      * f   = frequency (Bluetooth center frequency is 2.44175GHz = 2.44175x10^9 Hz);
-     * l   = wavelength (meters);
+     * l   = wavelength (in meters);
      * d   = distance (from transmitter to receiver in meters);
      * dB  = decibels
      * dBm = decibel milliwatts
@@ -40,124 +47,115 @@
      *
      * FSPL      = (4 * pi * d / l)^2 = (4 * pi * d * f / c)^2
      *
-     * FSPL (dB) = 10*log10((4 * pi * d * f / c)^2)
-     *           = 20*log10(4 * pi * d * f / c)
-     *           = 20*log10(d) + 20*log10(f) + 20*log10(4*pi/c)
+     * FSPL (dB) = 10 * log10((4 * pi * d  * f / c)^2)
+     *           = 20 * log10(4 * pi * d * f / c)
+     *           = (20 * log10(d)) + (20 * log10(f)) + (20 * log10(4 * pi/c))
      *
      * Calculating constants:
      *
-     * FSPL_FREQ        = 20*log10(f)
-     *                  = 20*log10(2.44175 * 10^9)
+     * FSPL_FREQ        = 20 * log10(f)
+     *                  = 20 * log10(2.44175 * 10^9)
      *                  = 187.75
      *
-     * FSPL_LIGHT       = 20*log10(4*pi/c)
-     *                  = 20*log10(4*pi/(2.9979*10^8))
+     * FSPL_LIGHT       = 20 * log10(4 * pi/c)
+     *                  = 20 * log10(4 * pi/(2.9979 * 10^8))
+     *                  = 20 * log10(4 * pi/(2.9979 * 10^8))
+     *                  = 20 * log10(41.9172441s * 10^-9)
      *                  = -147.55
      *
-     * FSPL_DISTANCE_1M = 20*log10(1m)
+     * FSPL_DISTANCE_1M = 20 * log10(1)
      *                  = 0
      *
      * PATH_LOSS_AT_1M  = FSPL_DISTANCE_1M + FSPL_FREQ + FSPL_LIGHT
-     *                  = 0                + 187.75    + (-147.55)
-     *                  = 40.20 [round to 41]
+     *                  =       0          + 187.75    + (-147.55)
+     *                  = 40.20db [round to 41db]
      *
-     * Note that PATH_LOSS_AT_1M is rounded to 41 instead to the more natural 40. The first version
-     * of this file had a typo that caused the value to be close to 41; when this was discovered,
-     * the value 41 was already used in many places, and it was more important to be consistent
-     * rather than exact.
-     *
-     * Given this we can work out a formula for distance from a given RSSI (received signal strength
-     * indicator) and a given value for the expected strength at one meter from the beacon (aka
-     * calibrated transmission power). Both values are in dBm.
-     *
-     * FSPL = 20*log10(d) + PATH_LOSS_AT_1M = full_power - RSSI
-     *        20*log10(d) + PATH_LOSS_AT_1M = power_at_1m + PATH_LOSS_AT_1M - RSSI
-     *        20*log10(d)                   = power_at_1m - RSSI
-     *           log10(d)                   = (power_at_1m - RSSI) / 20
-     *                 d                    = 10 ^ ((power_at_1m - RSSI) / 20)
-     *
-     * Note: because of how logarithms work, units get a bit funny. If you take a two values x and y
-     * whose units are dBm, the value of x - y has units of dB, not dBm. Similarly, if x is dBm and
-     * y is in dB, then x - y will be in dBm.
+     * Note: Rounding up makes us "closer" and makes us more aggressive at showing notifications.
      */
-
-    /* (dBm) PATH_LOSS at 1m for isotropic antenna transmitting BLE */
-    public static final int PATH_LOSS_AT_1M = 41;
-
-    /** Different region categories, based on distance range. */
-    public static final class Region {
-
-        public static final int UNKNOWN = -1;
-        public static final int NEAR = 0;
-        public static final int MID = 1;
-        public static final int FAR = 2;
-
-        private Region() {}
-    }
-
-    // Cutoff distances between different regions.
-    public static final double NEAR_TO_MID_METERS = 0.5;
-    public static final double MID_TO_FAR_METERS = 2.0;
-
-    public static final int DEFAULT_CALIBRATED_TX_POWER = -77;
-
-    private RangingUtils() {}
+    private static final int RSSI_DROP_OFF_AT_1_M = 41;
 
     /**
-     * Convert RSSI to path loss using the free space path loss equation. See <a
-     * href="http://en.wikipedia.org/wiki/Free-space_path_loss">Free-space_path_loss</a>
+     * Convert target distance and txPower to a RSSI value using the Log-distance path loss model
+     * with Path Loss at 1m of 41db.
      *
-     * @param rssi Received Signal Strength Indication (RSSI) in dBm
-     * @param calibratedTxPower the calibrated power of the transmitter (dBm) at 1 meter
-     * @return The calculated path loss.
+     * @return RSSI expected at distanceInMeters with device broadcasting at txPower.
      */
-    public static int pathLossFromRssi(int rssi, int calibratedTxPower) {
-        return calibratedTxPower + PATH_LOSS_AT_1M - rssi;
-    }
-
-    /**
-     * Convert RSSI to distance using the free space path loss equation. See <a
-     * href="http://en.wikipedia.org/wiki/Free-space_path_loss">Free-space_path_loss</a>
-     *
-     * @param rssi Received Signal Strength Indication (RSSI) in dBm
-     * @param calibratedTxPower the calibrated power of the transmitter (dBm) at 1 meter
-     * @return the distance at which that rssi value would occur in meters
-     */
-    public static double distanceFromRssi(int rssi, int calibratedTxPower) {
-        return Math.pow(10, (calibratedTxPower - rssi) / 20.0);
-    }
-
-    /**
-     * Determine the region of a beacon given its perceived distance.
-     *
-     * @param distance The measured distance in meters.
-     * @return the region as one of the constants in {@link Region}.
-     */
-    public static int regionFromDistance(double distance) {
-        if (distance < 0) {
-            return Region.UNKNOWN;
-        }
-        if (distance <= NEAR_TO_MID_METERS) {
-            return Region.NEAR;
-        }
-        if (distance <= MID_TO_FAR_METERS) {
-            return Region.MID;
-        }
-        return Region.FAR;
-    }
-
-    /**
-     * Convert distance to RSSI using the free space path loss equation. See <a
-     * href="http://en.wikipedia.org/wiki/Free-space_path_loss">Free-space_path_loss</a>
-     *
-     * @param distanceInMeters distance in meters (m)
-     * @param calibratedTxPower transmitted power (dBm) calibrated to 1 meter
-     * @return the rssi (dBm) that would be measured at that distance
-     */
-    public static int rssiFromDistance(double distanceInMeters, int calibratedTxPower) {
+    public static int rssiFromTargetDistance(double distanceInMeters, int txPower) {
+        /*
+         * See <a href="https://en.wikipedia.org/wiki/Log-distance_path_loss_model">
+         * Log-distance path loss model</a>.
+         *
+         * PL      = total path loss in db
+         * txPower = TxPower in dbm
+         * rssi    = Received signal strength in dbm
+         * PL_0    = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm
+         * d       = length of path
+         * d_0     = reference distance  (1 m)
+         * gamma   = path loss exponent (2 in free space)
+         *
+         * Log-distance path loss (LDPL) formula:
+         *
+         * PL = txPower - rssi =                   PL_0          + 10 * gamma  * log_10(d / d_0)
+         *      txPower - rssi =            RSSI_DROP_OFF_AT_1_M + 10 * 2 * log_10
+         * (distanceInMeters / 1)
+         *              - rssi = -txPower + RSSI_DROP_OFF_AT_1_M + 20 * log_10(distanceInMeters)
+         *                rssi =  txPower - RSSI_DROP_OFF_AT_1_M - 20 * log_10(distanceInMeters)
+         */
+        txPower = adjustPower(txPower);
         return distanceInMeters == 0
-                ? calibratedTxPower + PATH_LOSS_AT_1M
-                : (int) (calibratedTxPower - (20 * Math.log10(distanceInMeters)));
+                ? txPower
+                : (int) Math.floor((txPower - RSSI_DROP_OFF_AT_1_M)
+                        - 20 * Math.log10(distanceInMeters));
+    }
+
+    /**
+     * Convert RSSI and txPower to a distance value using the Log-distance path loss model with Path
+     * Loss at 1m of 41db.
+     *
+     * @return distance in meters with device broadcasting at txPower and given RSSI.
+     */
+    public static double distanceFromRssiAndTxPower(int rssi, int txPower) {
+        /*
+         * See <a href="https://en.wikipedia.org/wiki/Log-distance_path_loss_model">Log-distance
+         * path
+         * loss model</a>.
+         *
+         * PL      = total path loss in db
+         * txPower = TxPower in dbm
+         * rssi    = Received signal strength in dbm
+         * PL_0    = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm
+         * d       = length of path
+         * d_0     = reference distance  (1 m)
+         * gamma   = path loss exponent (2 in free space)
+         *
+         * Log-distance path loss (LDPL) formula:
+         *
+         * PL =    txPower - rssi                               = PL_0 + 10 * gamma  * log_10(d /
+         *  d_0)
+         *         txPower - rssi               = RSSI_DROP_OFF_AT_1_M + 10 * gamma  * log_10(d /
+         *  d_0)
+         *         txPower - rssi - RSSI_DROP_OFF_AT_1_M        = 10 * 2 * log_10
+         * (distanceInMeters / 1)
+         *         txPower - rssi - RSSI_DROP_OFF_AT_1_M        = 20 * log_10(distanceInMeters / 1)
+         *        (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20  = log_10(distanceInMeters)
+         *  10 ^ ((txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20) = distanceInMeters
+         */
+        txPower = adjustPower(txPower);
+        rssi = adjustPower(rssi);
+        return Math.pow(10, (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20.0);
+    }
+
+    /**
+     * Prevents the power from becoming too large or too small.
+     */
+    private static int adjustPower(int power) {
+        if (power > MAX_RSSI_VALUE) {
+            return MAX_RSSI_VALUE;
+        }
+        if (power < MIN_RSSI_VALUE) {
+            return MIN_RSSI_VALUE;
+        }
+        return power;
     }
 }
 
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index b175234..4de4cee 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -27,7 +27,9 @@
 import android.nearby.NearbyDevice;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.common.ble.decode.FastPairDecoder;
+import com.android.server.nearby.common.ble.util.RangingUtils;
 import com.android.server.nearby.common.bloomfilter.BloomFilter;
 import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
 import com.android.server.nearby.common.locator.Locator;
@@ -52,6 +54,8 @@
     String mBleAddress;
     // Need to be deleted after notification manager in use.
     private boolean mIsFirst = false;
+    private FastPairDataProvider mPairDataProvider;
+    private static final double NEARBY_DISTANCE_THRESHOLD = 0.6;
 
     /** The types about how the bloomfilter is processed. */
     public enum ProcessBloomFilterType {
@@ -68,6 +72,12 @@
         mContext = context;
     }
 
+    @VisibleForTesting
+    FastPairAdvHandler(Context context, FastPairDataProvider dataProvider) {
+        mContext = context;
+        mPairDataProvider = dataProvider;
+    }
+
     /**
      * Handles all of the scanner result. Fast Pair will handle model id broadcast bloomfilter
      * broadcast and battery level broadcast.
@@ -75,23 +85,32 @@
     public void handleBroadcast(NearbyDevice device) {
         FastPairDevice fastPairDevice = (FastPairDevice) device;
         mBleAddress = fastPairDevice.getBluetoothAddress();
-        FastPairDataProvider dataProvider = FastPairDataProvider.getInstance();
-        if (dataProvider == null) {
+        if (mPairDataProvider == null) {
+            mPairDataProvider = FastPairDataProvider.getInstance();
+        }
+        if (mPairDataProvider == null) {
             return;
         }
-        List<Account> accountList = dataProvider.loadFastPairEligibleAccounts();
+        List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
         if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
             byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
             Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
             // Use api to get anti spoofing key from model id.
             try {
                 Rpcs.GetObservedDeviceResponse response =
-                        dataProvider.loadFastPairAntispoofKeyDeviceMetadata(model);
+                        mPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(model);
                 if (response == null) {
                     Log.e(TAG, "server does not have model id "
                             + Hex.bytesToStringLowercase(model));
                     return;
                 }
+                // Check the distance of the device if the distance is larger than the threshold
+                // do not show half sheet.
+                if (!isNearby(fastPairDevice.getRssi(),
+                        response.getDevice().getBleTxPower() == 0 ? fastPairDevice.getTxPower()
+                                : response.getDevice().getBleTxPower())) {
+                    return;
+                }
                 Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
                         DataUtils.toScanFastPairStoreItem(
                                 response, mBleAddress,
@@ -111,7 +130,7 @@
                 }
                 for (Account account : accountList) {
                     List<Data.FastPairDeviceWithAccountKey> listDevices =
-                            dataProvider.loadFastPairDeviceWithAccountKey(account);
+                            mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
                     Data.FastPairDeviceWithAccountKey recognizedDevice =
                             findRecognizedDevice(listDevices,
                                     new BloomFilter(bloomFilterByteArray,
@@ -120,6 +139,15 @@
                     if (recognizedDevice != null) {
                         Log.d(TAG, "find matched device show notification to remind"
                                 + " user to pair");
+                        // Check the distance of the device if the distance is larger than the
+                        // threshold
+                        // do not show half sheet.
+                        if (!isNearby(fastPairDevice.getRssi(),
+                                recognizedDevice.getDiscoveryItem().getTxPower() == 0
+                                        ? fastPairDevice.getTxPower()
+                                        : recognizedDevice.getDiscoveryItem().getTxPower())) {
+                            return;
+                        }
                         // Check if the device is already paired
                         List<Cache.StoredFastPairItem> storedFastPairItemList =
                                 Locator.get(mContext, FastPairCacheManager.class)
@@ -141,8 +169,9 @@
                                 // Get full info from api the initial request will only return
                                 // part of the info due to size limit.
                                 List<Data.FastPairDeviceWithAccountKey> resList =
-                                        dataProvider.loadFastPairDeviceWithAccountKey(account,
-                                        List.of(recognizedDevice.getAccountKey().toByteArray()));
+                                        mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
+                                                List.of(recognizedDevice.getAccountKey()
+                                                        .toByteArray()));
                                 if (resList != null && resList.size() > 0) {
                                     //Saved device from footprint does not have ble address so
                                     // fill ble address with current scan result.
@@ -154,7 +183,7 @@
                                     Locator.get(mContext, FastPairController.class).pair(
                                             new DiscoveryItem(mContext, storedDiscoveryItem),
                                             resList.get(0).getAccountKey().toByteArray(),
-                                            /** companionApp=*/ null);
+                                            /** companionApp=*/null);
                                 }
                             }
                         }
@@ -212,4 +241,11 @@
         return null;
     }
 
+    /**
+     * Check the device distance for certain rssi value.
+     */
+    boolean isNearby(int rssi, int txPower) {
+        return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD;
+    }
+
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index 16c04de..2d7cba9 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -103,8 +103,9 @@
     private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-                Log.d(TAG, "onReceive: ACTION_SCREEN_ON.");
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)
+                    || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+                Log.d(TAG, "onReceive: ACTION_SCREEN_ON or boot complete.");
                 invalidateScan();
             } else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                 processBluetoothConnectionEvent(intent);
@@ -149,6 +150,7 @@
         mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
         mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        mIntentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
 
         mLocatorContextWrapper.getContext()
                 .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
index b8a9796..d975301 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
@@ -47,7 +47,7 @@
  */
 public class DiscoveryItem implements Comparable<DiscoveryItem> {
 
-    private static final String  ACTION_FAST_PAIR =
+    private static final String ACTION_FAST_PAIR =
             "com.android.server.nearby:ACTION_FAST_PAIR";
     private static final int BEACON_STALENESS_MILLIS = 120000;
     private static final int ITEM_EXPIRATION_MILLIS = 20000;
@@ -66,14 +66,15 @@
             Cache.StoredDiscoveryItem.State.STATE_DISABLED_BY_SYSTEM_VALUE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ItemState {}
+    public @interface ItemState {
+    }
 
     public DiscoveryItem(LocatorContextWrapper locatorContextWrapper,
             Cache.StoredDiscoveryItem mStoredDiscoveryItem) {
         this.mFastPairCacheManager =
                 locatorContextWrapper.getLocator().get(FastPairCacheManager.class);
         this.mClock =
-            locatorContextWrapper.getLocator().get(Clock.class);
+                locatorContextWrapper.getLocator().get(Clock.class);
         this.mStoredDiscoveryItem = mStoredDiscoveryItem;
     }
 
@@ -160,6 +161,7 @@
                 .setLastUserExperience(experienceType).build();
         mFastPairCacheManager.saveDiscoveryItem(this);
     }
+
     /**
      * Gets the user experience to be good or bad.
      */
@@ -312,6 +314,7 @@
     }
 
     // Getters below
+
     /**
      * Returns the id of store discovery item.
      */
@@ -376,7 +379,7 @@
         Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl());
         if (intent == null) {
             Log.d("FastPairDiscoveryItem", "FastPair: fail to parse action url "
-                            + mStoredDiscoveryItem.getActionUrl());
+                    + mStoredDiscoveryItem.getActionUrl());
             return null;
         }
         return intent.getStringExtra(EXTRA_FAST_PAIR_SECRET);
@@ -472,7 +475,7 @@
     @Nullable
     public Double getEstimatedDistance() {
         // In the future, we may want to do a foreground subscription to leverage onDistanceChanged.
-        return RangingUtils.distanceFromRssi(mStoredDiscoveryItem.getRssi(),
+        return RangingUtils.distanceFromRssiAndTxPower(mStoredDiscoveryItem.getRssi(),
                 mStoredDiscoveryItem.getTxPower());
     }
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
index 2926015..ebe72b3 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
@@ -32,17 +32,14 @@
     public void distanceFromRssi_getCorrectValue() {
         // Distance expected to be 1.0 meters based on an RSSI/TxPower of -41dBm
         // Using params: int rssi (dBm), int calibratedTxPower (dBm)
-        double distance = RangingUtils.distanceFromRssi(-41, -41);
+        double distance = RangingUtils.distanceFromRssiAndTxPower(-82, -41);
         assertThat(distance).isWithin(DELTA).of(1.0);
 
-        double distance2 = RangingUtils.distanceFromRssi(-70, -50);
+        double distance2 = RangingUtils.distanceFromRssiAndTxPower(-111, -50);
         assertThat(distance2).isWithin(DELTA).of(10.0);
 
-        // testing that the double values are not casted to integers
-        double distance3 = RangingUtils.distanceFromRssi(-67, -77);
-        assertThat(distance3).isWithin(DELTA).of(0.31622776601683794);
-
-        double distance4 = RangingUtils.distanceFromRssi(-50, -70);
+        //rssi txpower
+        double distance4 = RangingUtils.distanceFromRssiAndTxPower(-50, -29);
         assertThat(distance4).isWithin(DELTA).of(0.1);
     }
 
@@ -50,8 +47,17 @@
     public void testRssiFromDistance() {
         // RSSI expected at 1 meter based on the calibrated tx field of -41dBm
         // Using params: distance (m), int calibratedTxPower (dBm),
-        int rssi = RangingUtils.rssiFromDistance(1.0, -41);
+        int rssi = RangingUtils.rssiFromTargetDistance(1.0, -41);
 
-        assertThat(rssi).isEqualTo(-41);
+        assertThat(rssi).isEqualTo(-82);
+    }
+
+    @Test
+    public void testOutOfRange() {
+        double distance = RangingUtils.distanceFromRssiAndTxPower(-200, -41);
+        assertThat(distance).isWithin(DELTA).of(177.82794);
+
+        distance = RangingUtils.distanceFromRssiAndTxPower(200, -41);
+        assertThat(distance).isWithin(DELTA).of(0);
     }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
new file mode 100644
index 0000000..346a961
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.nearby.FastPairDevice;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.provider.FastPairDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Rpcs;
+
+public class FastPairAdvHandlerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private FastPairDataProvider mFastPairDataProvider;
+    @Mock
+    private FastPairHalfSheetManager mFastPairHalfSheetManager;
+    @Mock
+    private FastPairNotificationManager mFastPairNotificationManager;
+    private static final String BLUETOOTH_ADDRESS = "AA:BB:CC:DD";
+    private static final int CLOSE_RSSI = -80;
+    private static final int FAR_AWAY_RSSI = -120;
+    private static final int TX_POWER = -70;
+    private static final byte[] INITIAL_BYTE_ARRAY = new byte[]{0x01, 0x02, 0x03};
+
+    LocatorContextWrapper mLocatorContextWrapper;
+    FastPairAdvHandler mFastPairAdvHandler;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mLocatorContextWrapper = new LocatorContextWrapper(mContext);
+        mLocatorContextWrapper.getLocator().overrideBindingForTest(
+                FastPairHalfSheetManager.class, mFastPairHalfSheetManager
+        );
+        mLocatorContextWrapper.getLocator().overrideBindingForTest(
+                FastPairNotificationManager.class, mFastPairNotificationManager
+        );
+        when(mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any()))
+                .thenReturn(Rpcs.GetObservedDeviceResponse.getDefaultInstance());
+        mFastPairAdvHandler = new FastPairAdvHandler(mLocatorContextWrapper, mFastPairDataProvider);
+    }
+
+    @Test
+    public void testInitialBroadcast() {
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(INITIAL_BYTE_ARRAY)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setRssi(CLOSE_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairHalfSheetManager).showHalfSheet(any());
+    }
+
+    @Test
+    public void testInitialBroadcast_farAway_notShowHalfSheet() {
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(INITIAL_BYTE_ARRAY)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setRssi(FAR_AWAY_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
+    }
+
+    @Test
+    public void testSubsequentBroadcast() {
+        byte[] fastPairRecordWithBloomFilter =
+                new byte[]{
+                        (byte) 0x02,
+                        (byte) 0x01,
+                        (byte) 0x02, // Flags
+                        (byte) 0x02,
+                        (byte) 0x0A,
+                        (byte) 0xEB, // Tx Power (-20)
+                        (byte) 0x0B,
+                        (byte) 0x16,
+                        (byte) 0x2C,
+                        (byte) 0xFE, // FastPair Service Data
+                        (byte) 0x00, // Flags (model ID length = 3)
+                        (byte) 0x40, // Account key hash flags (length = 4, type = 0)
+                        (byte) 0x11,
+                        (byte) 0x22,
+                        (byte) 0x33,
+                        (byte) 0x44, // Account key hash (0x11223344)
+                        (byte) 0x11, // Account key salt flags (length = 1, type = 1)
+                        (byte) 0x55, // Account key salt
+                };
+        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
+                .setData(fastPairRecordWithBloomFilter)
+                .setBluetoothAddress(BLUETOOTH_ADDRESS)
+                .setRssi(CLOSE_RSSI)
+                .setTxPower(TX_POWER)
+                .build();
+
+        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
+
+        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
+    }
+}