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());
+ }
+}