Merge "Add more file to make the pairing flow work"
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
new file mode 100644
index 0000000..321e125
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.ble.util;
+
+
+/**
+ * Ranging utilities embody the physics of converting RF path loss to distance. The free space path
+ * loss is proportional to the square of the distance from transmitter to receiver, and to the
+ * square of the frequency of the propagation signal.
+ */
+public final class RangingUtils {
+ /*
+ * Key to variable names used in this class (viz. Physics):
+ *
+ * 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);
+ * d = distance (from transmitter to receiver in meters);
+ * dB = decibels
+ * dBm = decibel milliwatts
+ *
+ *
+ * Free-space path loss (FSPL) is proportional to the square of the distance between the
+ * transmitter and the receiver, and also proportional to the square of the frequency of the
+ * radio signal.
+ *
+ * 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)
+ *
+ * Calculating constants:
+ *
+ * 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))
+ * = -147.55
+ *
+ * FSPL_DISTANCE_1M = 20*log10(1m)
+ * = 0
+ *
+ * PATH_LOSS_AT_1M = FSPL_DISTANCE_1M + FSPL_FREQ + FSPL_LIGHT
+ * = 0 + 187.75 + (-147.55)
+ * = 40.20 [round to 41]
+ *
+ * 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.
+ */
+
+ /* (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() {}
+
+ /**
+ * 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>
+ *
+ * @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.
+ */
+ 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) {
+ return distanceInMeters == 0
+ ? calibratedTxPower + PATH_LOSS_AT_1M
+ : (int) (calibratedTxPower - (20 * Math.log10(distanceInMeters)));
+ }
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
index 440d126..2658bec 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
@@ -402,7 +402,7 @@
Log.i(TAG, "getProviderDeviceName failed, pairingSecret == null.");
return null;
}
- @Nullable String deviceName = mDeviceNameReceiver.getParsedResult(mPairingSecret);
+ String deviceName = mDeviceNameReceiver.getParsedResult(mPairingSecret);
Log.i(TAG, "getProviderDeviceName = " + deviceName);
return deviceName;
diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java b/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java
new file mode 100644
index 0000000..35a1a9f
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.fastpair;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.ColorUtils;
+
+/** Utility methods for icon size verification. */
+public class IconUtils {
+ private static final int MIN_ICON_SIZE = 16;
+ private static final int DESIRED_ICON_SIZE = 32;
+ private static final double NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE = 0.125;
+ private static final double NOTIFICATION_BACKGROUND_ALPHA = 0.7;
+
+ /**
+ * Verify that the icon is non null and falls in the small bucket. Just because an icon isn't
+ * small doesn't guarantee it is large or exists.
+ */
+ @VisibleForTesting
+ static boolean isIconSizedSmall(@Nullable Bitmap bitmap) {
+ if (bitmap == null) {
+ return false;
+ }
+ int min = MIN_ICON_SIZE;
+ int desired = DESIRED_ICON_SIZE;
+ return bitmap.getWidth() >= min
+ && bitmap.getWidth() < desired
+ && bitmap.getHeight() >= min
+ && bitmap.getHeight() < desired;
+ }
+
+ /**
+ * Verify that the icon is non null and falls in the regular / default size bucket. Doesn't
+ * guarantee if not regular then it is small.
+ */
+ @VisibleForTesting
+ static boolean isIconSizedRegular(@Nullable Bitmap bitmap) {
+ if (bitmap == null) {
+ return false;
+ }
+ return bitmap.getWidth() >= DESIRED_ICON_SIZE
+ && bitmap.getHeight() >= DESIRED_ICON_SIZE;
+ }
+
+ // All icons that are sized correctly (larger than the min icon size) are resize on the server
+ // to the desired icon size so that they appear correct in notifications.
+
+ /**
+ * All icons that are sized correctly (larger than the min icon size) are resize on the server
+ * to the desired icon size so that they appear correct in notifications.
+ */
+ public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) {
+ if (bitmap == null) {
+ return false;
+ }
+ return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap);
+ }
+
+ /** Adds a circular, white background to the bitmap. */
+ @Nullable
+ public static Bitmap addWhiteCircleBackground(Context context, @Nullable Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+
+ if (bitmap.getWidth() != bitmap.getHeight()) {
+ return bitmap;
+ }
+
+ int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE);
+ Bitmap bitmapWithBackground =
+ Bitmap.createBitmap(
+ bitmap.getWidth() + (2 * padding),
+ bitmap.getHeight() + (2 * padding),
+ bitmap.getConfig());
+ Canvas canvas = new Canvas(bitmapWithBackground);
+ Paint paint = new Paint();
+ paint.setColor(
+ ColorUtils.setAlphaComponent(
+ Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA)));
+ paint.setStyle(Paint.Style.FILL);
+ paint.setAntiAlias(true);
+ canvas.drawCircle(
+ bitmapWithBackground.getWidth() / 2f,
+ bitmapWithBackground.getHeight() / 2f,
+ bitmapWithBackground.getWidth() / 2f,
+ paint);
+ canvas.drawBitmap(bitmap, padding, padding, null);
+ return bitmapWithBackground;
+ }
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java b/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
new file mode 100644
index 0000000..c50e219
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.fastpair.service;
+
+/** Handles intents to {@link com.android.server.nearby.fastpair.FastPairManager}. */
+public class UserActionHandlerBase {
+ public static final String PREFIX = "com.android.server.nearby.fastpair.";
+ public static final String ACTION_PREFIX = "com.android.server.nearby:";
+
+ public static final String EXTRA_ITEM_ID = PREFIX + "EXTRA_ITEM_ID";
+ public static final String EXTRA_COMPANION_APP = ACTION_PREFIX + "EXTRA_COMPANION_APP";
+
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
new file mode 100644
index 0000000..85f9ee4
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BroadcastConstants.EXTRA_RETROACTIVE_PAIR;
+import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
+import static com.android.server.nearby.fastpair.FastPairManager.EXTRA_NOTIFICATION_ID;
+
+import static com.google.common.io.BaseEncoding.base16;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.server.nearby.common.eventloop.Annotations;
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.common.eventloop.NamedRunnable;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import service.proto.Cache;
+
+/**
+ * FastPair controller after get the info from intent handler Fast Pair controller is responsible
+ * for pairing control.
+ */
+public class FastPairController {
+ private final Context mContext;
+ private final EventLoop mEventLoop;
+ private final FastPairCacheManager mFastPairCacheManager;
+ private final FootprintsDeviceManager mFootprintsDeviceManager;
+ private boolean mIsFastPairing = false;
+ @Nullable private Callback mCallback;
+ public FastPairController(Context context) {
+ mContext = context;
+ mEventLoop = Locator.get(mContext, EventLoop.class);
+ mFastPairCacheManager = Locator.get(mContext, FastPairCacheManager.class);
+ mFootprintsDeviceManager = Locator.get(mContext, FootprintsDeviceManager.class);
+ }
+
+ /**
+ * Should be called on create lifecycle.
+ */
+ @WorkerThread
+ public void onCreate() {
+ mEventLoop.postRunnable(new NamedRunnable("FastPairController::InitializeScanner") {
+ @Override
+ public void run() {
+ // init scanner here and start scan.
+ }
+ });
+ }
+
+ /**
+ * Should be called on destroy lifecycle.
+ */
+ @WorkerThread
+ public void onDestroy() {
+ mEventLoop.postRunnable(new NamedRunnable("FastPairController::DestroyScanner") {
+ @Override
+ public void run() {
+ // Unregister scanner from here
+ }
+ });
+ }
+
+ /**
+ * Pairing function.
+ */
+ @UiThread
+ public void pair(Intent intent) {
+ String itemId = intent.getStringExtra(UserActionHandler.EXTRA_ITEM_ID);
+ int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+ byte[] discoveryItem = intent.getByteArrayExtra(UserActionHandler.EXTRA_DISCOVERY_ITEM);
+ String accountKeyString = intent.getStringExtra(UserActionHandler.EXTRA_FAST_PAIR_SECRET);
+ String companionApp = trimCompanionApp(intent.getStringExtra(EXTRA_COMPANION_APP));
+ byte[] accountKey = accountKeyString != null ? base16().decode(accountKeyString) : null;
+ boolean isRetroactivePair = intent.getBooleanExtra(EXTRA_RETROACTIVE_PAIR, false);
+
+ mEventLoop.postRunnable(
+ new NamedRunnable("fastPairWith=" + itemId) {
+ @Override
+ public void run() {
+ DiscoveryItem item = null;
+ if (itemId != null) {
+ // api call to get Fast Pair related info
+ item = mFastPairCacheManager.getDiscoveryItem(itemId);
+ } else if (discoveryItem != null) {
+ try {
+ item =
+ new DiscoveryItem(
+ mContext,
+ Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
+ } catch (InvalidProtocolBufferException e) {
+ Log.w("FastPairController",
+ "Error parsing serialized discovery item with size "
+ + discoveryItem.length);
+ }
+ }
+
+
+ if (item == null || TextUtils.isEmpty(item.getMacAddress())) {
+ Log.w("FastPairController",
+ "Invalid DiscoveryItem, ignore pairing");
+ return;
+ }
+
+ // Check enabled state to prevent multiple pair attempts if we get the
+ // intent more than once (this can happen due to an Android platform
+ // bug - b/31459521).
+ if (item.getState() != Cache.StoredDiscoveryItem.State.STATE_ENABLED
+ && !isRetroactivePair) {
+ Log.d("FastPairController", "Incorrect state, ignore pairing");
+ return;
+ }
+
+ boolean useLargeNotifications = accountKey != null
+ || item.getAuthenticationPublicKeySecp256R1() != null;
+ FastPairNotificationManager fastPairNotificationManager =
+ notificationId == -1
+ ? new FastPairNotificationManager(mContext, item,
+ useLargeNotifications)
+ : new FastPairNotificationManager(mContext, item,
+ useLargeNotifications, notificationId);
+ FastPairHalfSheetManager fastPairHalfSheetManager =
+ Locator.get(mContext, FastPairHalfSheetManager.class);
+
+ mFastPairCacheManager.saveDiscoveryItem(item);
+
+ PairingProgressHandlerBase pairingProgressHandlerBase =
+ PairingProgressHandlerBase.create(
+ mContext,
+ item,
+ companionApp,
+ accountKey,
+ mFootprintsDeviceManager,
+ fastPairNotificationManager,
+ fastPairHalfSheetManager,
+ isRetroactivePair);
+
+ pair(item, accountKey, companionApp, pairingProgressHandlerBase);
+ }
+ });
+ }
+
+ /**
+ * Pairing function
+ */
+ @Annotations.EventThread
+ public void pair(
+ DiscoveryItem item,
+ @Nullable byte[] accountKey,
+ @Nullable String companionApp,
+ PairingProgressHandlerBase pairingProgressHandlerBase) {
+ if (mIsFastPairing) {
+ Log.d("FastPairController", "FastPair: fastpairing, skip pair request");
+ return;
+ }
+ Log.d("FastPairController", "FastPair: start pair");
+
+ // Hide all "tap to pair" notifications until after the flow completes.
+ mEventLoop.removeRunnable(mReEnableAllDeviceItemsRunnable);
+ if (mCallback != null) {
+ mCallback.fastPairUpdateDeviceItemsEnabled(false);
+ }
+
+ Future<Void> task =
+ FastPairManager.pair(
+ Executors.newSingleThreadExecutor(),
+ mContext,
+ item,
+ accountKey,
+ companionApp,
+ mFootprintsDeviceManager,
+ pairingProgressHandlerBase);
+ mIsFastPairing = true;
+ }
+
+ /** Fixes a companion app package name with extra spaces. */
+ private static String trimCompanionApp(String companionApp) {
+ return companionApp == null ? null : companionApp.trim();
+ }
+
+ private final NamedRunnable mReEnableAllDeviceItemsRunnable =
+ new NamedRunnable("reEnableAllDeviceItems") {
+ @Override
+ public void run() {
+ if (mCallback != null) {
+ mCallback.fastPairUpdateDeviceItemsEnabled(true);
+ }
+ }
+ };
+
+ interface Callback {
+ void fastPairUpdateDeviceItemsEnabled(boolean enabled);
+ }
+}
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 e382f84..d406678 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,15 +16,42 @@
package com.android.server.nearby.fastpair;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.app.KeyguardManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection;
+import com.android.server.nearby.common.bluetooth.fastpair.PairingException;
+import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
+import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException;
+import com.android.server.nearby.common.bluetooth.fastpair.SimpleBroadcastReceiver;
+import com.android.server.nearby.common.eventloop.Annotations;
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.common.eventloop.NamedRunnable;
import com.android.server.nearby.common.locator.Locator;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
+
+import java.security.GeneralSecurityException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import service.proto.Rpcs;
@@ -32,11 +59,18 @@
* FastPairManager is the class initiated in nearby service to handle Fast Pair related
* work.
*/
+
public class FastPairManager {
+ private static final String ACTION_PREFIX = UserActionHandler.PREFIX;
+ private static final int WAIT_FOR_UNLOCK_MILLIS = 5000;
+ /** A notification ID which should be dismissed*/
+ public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
+ private static Executor sFastPairExecutor;
+
LocatorContextWrapper mLocatorContextWrapper;
IntentFilter mIntentFilter;
Locator mLocator;
- private BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
@@ -78,4 +112,162 @@
public void cleanUp() {
mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver);
}
+
+
+ /**
+ * Starts fast pair process.
+ */
+ @Annotations.EventThread
+ public static Future<Void> pair(
+ ExecutorService executor,
+ Context context,
+ DiscoveryItem item,
+ @Nullable byte[] accountKey,
+ @Nullable String companionApp,
+ FootprintsDeviceManager footprints,
+ PairingProgressHandlerBase pairingProgressHandlerBase) {
+
+ return executor.submit(
+ () -> pairInternal(context, item, companionApp, accountKey, footprints,
+ pairingProgressHandlerBase), /* result= */ null);
+ }
+
+ /**
+ * Starts fast pair
+ */
+ @WorkerThread
+ public static void pairInternal(
+ Context context,
+ DiscoveryItem item,
+ @Nullable String companionApp,
+ @Nullable byte[] accountKey,
+ FootprintsDeviceManager footprints,
+ PairingProgressHandlerBase pairingProgressHandlerBase) {
+
+ try {
+ pairingProgressHandlerBase.onPairingStarted();
+ if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) {
+ // Do nothing due to we are not showing the status notification in some pairing
+ // types, e.g. the retroactive pairing.
+ } else {
+ // If the screen is locked when the user taps to pair, the screen will unlock. We
+ // must wait for the unlock to complete before showing the status notification, or
+ // it won't be heads-up.
+ pairingProgressHandlerBase.onWaitForScreenUnlock();
+ waitUntilScreenIsUnlocked(context);
+ pairingProgressHandlerBase.onScreenUnlocked();
+ }
+ BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(context);
+
+ boolean isBluetoothEnabled = bluetoothAdapter != null && bluetoothAdapter.isEnabled();
+ if (!isBluetoothEnabled) {
+ if (bluetoothAdapter == null || !bluetoothAdapter.enable()) {
+ Log.d("FastPairManager", "FastPair: Failed to enable bluetooth");
+ return;
+ }
+ Log.v("FastPairManager", "FastPair: Enabling bluetooth for fast pair");
+
+ Locator.get(context, EventLoop.class)
+ .postRunnable(
+ new NamedRunnable("enableBluetoothToast") {
+ @Override
+ public void run() {
+ Log.d("FastPairManager",
+ "Enable bluetooth toast test");
+ }
+ });
+ // Set up call back to call this function again once bluetooth has been
+ // enabled; this does not seem to be a problem as the device connects without a
+ // problem, but in theory the timeout also includes turning on bluetooth now.
+ }
+
+ pairingProgressHandlerBase.onReadyToPair();
+
+ String modelId = item.getTriggerId();
+ Preferences.Builder prefsBuilder =
+ FlagUtils.getPreferencesBuilder()
+ .setEnableBrEdrHandover(false)
+ .setIgnoreDiscoveryError(true);
+ pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
+ if (item.getFastPairInformation() != null) {
+ prefsBuilder.setSkipConnectingProfiles(
+ item.getFastPairInformation().getDataOnlyConnection());
+ }
+ FastPairConnection connection = new FastPairDualConnection(
+ context, item.getMacAddress(), prefsBuilder.build(), null);
+ pairingProgressHandlerBase.onPairingSetupCompleted();
+
+ FastPairConnection.SharedSecret sharedSecret;
+ if ((accountKey != null || item.getAuthenticationPublicKeySecp256R1() != null)) {
+ sharedSecret =
+ connection.pair(
+ accountKey != null ? accountKey
+ : item.getAuthenticationPublicKeySecp256R1());
+
+ byte[] key = pairingProgressHandlerBase.getKeyForLocalCache(accountKey,
+ connection, sharedSecret);
+
+ // We don't cache initial pairing case here but cache it when upload to footprints.
+ if (key != null) {
+ // CacheManager to save the content here
+ }
+ } else {
+ connection.pair();
+ }
+ pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress());
+ } catch (BluetoothException
+ | InterruptedException
+ | ReflectionException
+ | TimeoutException
+ | ExecutionException
+ | PairingException
+ | GeneralSecurityException e) {
+ Log.e("FastPairManager", "FastPair: Error");
+ pairingProgressHandlerBase.onPairingFailed(e);
+ }
+ }
+
+ /** Check if the pairing is initial pairing with fast pair 2.0 design. */
+ public static boolean isThroughFastPair2InitialPairing(
+ DiscoveryItem item, @Nullable byte[] accountKey) {
+ return accountKey == null && item.getAuthenticationPublicKeySecp256R1() != null;
+ }
+
+ private static void waitUntilScreenIsUnlocked(Context context)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+
+ // KeyguardManager's isScreenLocked() counterintuitively returns false when the lock screen
+ // is showing if the user has set "swipe to unlock" (i.e. no required password, PIN, or
+ // pattern) So we use this method instead, which returns true when on the lock screen
+ // regardless.
+ if (keyguardManager.isKeyguardLocked()) {
+ Log.v("FastPairManager",
+ "FastPair: Screen is locked, waiting until unlocked "
+ + "to show status notifications.");
+ try (SimpleBroadcastReceiver isUnlockedReceiver =
+ SimpleBroadcastReceiver.oneShotReceiver(
+ context, FlagUtils.getPreferencesBuilder().build(),
+ Intent.ACTION_USER_PRESENT)) {
+ isUnlockedReceiver.await(WAIT_FOR_UNLOCK_MILLIS, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ private static Executor getExecutor() {
+ if (sFastPairExecutor != null) {
+ return sFastPairExecutor;
+ }
+ sFastPairExecutor = Executors.newSingleThreadExecutor();
+ return sFastPairExecutor;
+ }
+
+ /**
+ * Helper function to get bluetooth adapter.
+ */
+ @Nullable
+ public static BluetoothAdapter getBluetoothAdapter(Context context) {
+ BluetoothManager manager = context.getSystemService(BluetoothManager.class);
+ return manager == null ? null : manager.getAdapter();
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
index 59edac0..5444e82 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
@@ -22,6 +22,10 @@
import com.android.server.nearby.common.locator.Module;
import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+
/**
* Module that associates all of the fast pair related singleton class
*/
@@ -33,6 +37,27 @@
public void configure(Context context, Class<?> type, Locator locator) {
if (type.equals(FastPairCacheManager.class)) {
locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context));
+ } else if (type.equals(FastPairController.class)) {
+ locator.bind(FastPairController.class, new FastPairController(context));
+ } else if (type.equals(FastPairCacheManager.class)) {
+ locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context));
+ } else if (type.equals(Clock.class)) {
+ locator.bind(Clock.class, new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return null;
+ }
+
+ @Override
+ public Clock withZone(ZoneId zone) {
+ return null;
+ }
+
+ @Override
+ public Instant instant() {
+ return null;
+ }
+ });
}
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java b/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java
new file mode 100644
index 0000000..883a1f8
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair;
+
+import android.text.TextUtils;
+
+import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * This is fast pair connection preference
+ */
+public class FlagUtils {
+ private static final int GATT_OPERATION_TIME_OUT_SECOND = 10;
+ private static final int GATT_CONNECTION_TIME_OUT_SECOND = 15;
+ private static final int BLUETOOTH_TOGGLE_TIME_OUT_SECOND = 10;
+ private static final int BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND = 2;
+ private static final int CLASSIC_DISCOVERY_TIME_OUT_SECOND = 13;
+ private static final int NUM_DISCOVER_ATTEMPTS = 3;
+ private static final int DISCOVERY_RETRY_SLEEP_SECONDS = 1;
+ private static final int SDP_TIME_OUT_SECONDS = 10;
+ private static final int NUM_SDP_ATTEMPTS = 0;
+ private static final int NUM_CREATED_BOND_ATTEMPTS = 3;
+ private static final int NUM_CONNECT_ATTEMPT = 2;
+ private static final int NUM_WRITE_ACCOUNT_KEY_ATTEMPT = 3;
+ private static final boolean TOGGLE_BLUETOOTH_ON_FAILURE = false;
+ private static final boolean BLUETOOTH_STATE_POOLING = true;
+ private static final int BLUETOOTH_STATE_POOLING_MILLIS = 1000;
+ private static final int NUM_ATTEMPTS = 2;
+ private static final short BREDR_HANDOVER_DATA_CHARACTERISTIC_ID = 11265; // 0x2c01
+ private static final short BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID = 11266; // 0x2c02
+ private static final short TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID = 11267; // 0x2c03
+ private static final boolean WAIT_FOR_UUID_AFTER_BONDING = true;
+ private static final boolean RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE = true;
+ private static final int REMOVE_BOND_TIME_OUT_SECONDS = 5;
+ private static final int REMOVE_BOND_SLEEP_MILLIS = 1000;
+ private static final int CREATE_BOND_TIME_OUT_SECONDS = 15;
+ private static final int HIDE_CREATED_BOND_TIME_OUT_SECONDS = 40;
+ private static final int PROXY_TIME_OUT_SECONDS = 2;
+ private static final boolean REJECT_ACCESS = false;
+ private static final boolean ACCEPT_PASSKEY = true;
+ private static final int WRITE_ACCOUNT_KEY_SLEEP_MILLIS = 2000;
+ private static final boolean PROVIDER_INITIATE_BONDING = false;
+ private static final boolean SPECIFY_CREATE_BOND_TRANSPORT_TYPE = false;
+ private static final int CREATE_BOND_TRANSPORT_TYPE = 0;
+ private static final boolean KEEP_SAME_ACCOUNT_KEY_WRITE = true;
+ private static final boolean ENABLE_NAMING_CHARACTERISTIC = true;
+ private static final boolean CHECK_FIRMWARE_VERSION = true;
+ private static final int SDP_ATTEMPTS_AFTER_BONDED = 1;
+ private static final boolean SUPPORT_HID = false;
+ private static final boolean ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING = true;
+ private static final boolean ACCEPT_CONSENT_FOR_FP_ONE = true;
+ private static final int GATT_CONNECT_RETRY_TIMEOUT_MILLIS = 18000;
+ private static final boolean ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC = true;
+ private static final boolean ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR = true;
+ private static final boolean ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE = true;
+ private static final boolean CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE = true;
+ private static final boolean MORE_LOG_FOR_QUALITY = true;
+ private static final boolean RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE = true;
+ private static final int GATT_CONNECT_SHORT_TIMEOUT_MS = 7000;
+ private static final int GATT_CONNECTION_LONG_TIME_OUT_MS = 15000;
+ private static final int GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 1000;
+ private static final int ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS = 15000;
+ private static final int PAIRING_RETRY_DELAY_MS = 100;
+ private static final int HANDSHAKE_SHORT_TIMEOUT_MS = 3000;
+ private static final int HANDSHAKE_LONG_TIMEOUT_MS = 1000;
+ private static final int SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 5000;
+ private static final int SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 7000;
+ private static final int SECRET_HANDSHAKE_RETRY_ATTEMPTS = 3;
+ private static final int SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS = 15000;
+ private static final int SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS = 15000;
+ private static final boolean RETRY_SECRET_HANDSHAKE_TIMEOUT = false;
+ private static final boolean LOG_USER_MANUAL_RETRY = true;
+ private static final boolean ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION = false;
+ private static final boolean LOG_USER_MANUAL_CITY = true;
+ private static final boolean LOG_PAIR_WITH_CACHED_MODEL_ID = true;
+ private static final boolean DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE = false;
+
+ public static Preferences.Builder getPreferencesBuilder() {
+ return Preferences.builder()
+ .setGattOperationTimeoutSeconds(GATT_OPERATION_TIME_OUT_SECOND)
+ .setGattConnectionTimeoutSeconds(GATT_CONNECTION_TIME_OUT_SECOND)
+ .setBluetoothToggleTimeoutSeconds(BLUETOOTH_TOGGLE_TIME_OUT_SECOND)
+ .setBluetoothToggleSleepSeconds(BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND)
+ .setClassicDiscoveryTimeoutSeconds(CLASSIC_DISCOVERY_TIME_OUT_SECOND)
+ .setNumDiscoverAttempts(NUM_DISCOVER_ATTEMPTS)
+ .setDiscoveryRetrySleepSeconds(DISCOVERY_RETRY_SLEEP_SECONDS)
+ .setSdpTimeoutSeconds(SDP_TIME_OUT_SECONDS)
+ .setNumSdpAttempts(NUM_SDP_ATTEMPTS)
+ .setNumCreateBondAttempts(NUM_CREATED_BOND_ATTEMPTS)
+ .setNumConnectAttempts(NUM_CONNECT_ATTEMPT)
+ .setNumWriteAccountKeyAttempts(NUM_WRITE_ACCOUNT_KEY_ATTEMPT)
+ .setToggleBluetoothOnFailure(TOGGLE_BLUETOOTH_ON_FAILURE)
+ .setBluetoothStateUsesPolling(BLUETOOTH_STATE_POOLING)
+ .setBluetoothStatePollingMillis(BLUETOOTH_STATE_POOLING_MILLIS)
+ .setNumAttempts(NUM_ATTEMPTS)
+ .setBrHandoverDataCharacteristicId(BREDR_HANDOVER_DATA_CHARACTERISTIC_ID)
+ .setBluetoothSigDataCharacteristicId(BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID)
+ .setBrTransportBlockDataDescriptorId(TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID)
+ .setWaitForUuidsAfterBonding(WAIT_FOR_UUID_AFTER_BONDING)
+ .setReceiveUuidsAndBondedEventBeforeClose(
+ RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE)
+ .setRemoveBondTimeoutSeconds(REMOVE_BOND_TIME_OUT_SECONDS)
+ .setRemoveBondSleepMillis(REMOVE_BOND_SLEEP_MILLIS)
+ .setCreateBondTimeoutSeconds(CREATE_BOND_TIME_OUT_SECONDS)
+ .setHidCreateBondTimeoutSeconds(HIDE_CREATED_BOND_TIME_OUT_SECONDS)
+ .setProxyTimeoutSeconds(PROXY_TIME_OUT_SECONDS)
+ .setRejectPhonebookAccess(REJECT_ACCESS)
+ .setRejectMessageAccess(REJECT_ACCESS)
+ .setRejectSimAccess(REJECT_ACCESS)
+ .setAcceptPasskey(ACCEPT_PASSKEY)
+ .setWriteAccountKeySleepMillis(WRITE_ACCOUNT_KEY_SLEEP_MILLIS)
+ .setProviderInitiatesBondingIfSupported(PROVIDER_INITIATE_BONDING)
+ .setAttemptDirectConnectionWhenPreviouslyBonded(true)
+ .setAutomaticallyReconnectGattWhenNeeded(true)
+ .setSkipDisconnectingGattBeforeWritingAccountKey(true)
+ .setIgnoreUuidTimeoutAfterBonded(true)
+ .setSpecifyCreateBondTransportType(SPECIFY_CREATE_BOND_TRANSPORT_TYPE)
+ .setCreateBondTransportType(CREATE_BOND_TRANSPORT_TYPE)
+ .setIncreaseIntentFilterPriority(true)
+ .setEvaluatePerformance(false)
+ .setKeepSameAccountKeyWrite(KEEP_SAME_ACCOUNT_KEY_WRITE)
+ .setEnableNamingCharacteristic(ENABLE_NAMING_CHARACTERISTIC)
+ .setEnableFirmwareVersionCharacteristic(CHECK_FIRMWARE_VERSION)
+ .setNumSdpAttemptsAfterBonded(SDP_ATTEMPTS_AFTER_BONDED)
+ .setSupportHidDevice(SUPPORT_HID)
+ .setEnablePairingWhileDirectlyConnecting(
+ ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING)
+ .setAcceptConsentForFastPairOne(ACCEPT_CONSENT_FOR_FP_ONE)
+ .setGattConnectRetryTimeoutMillis(GATT_CONNECT_RETRY_TIMEOUT_MILLIS)
+ .setEnable128BitCustomGattCharacteristicsId(
+ ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC)
+ .setEnableSendExceptionStepToValidator(ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR)
+ .setEnableAdditionalDataTypeWhenActionOverBle(
+ ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE)
+ .setCheckBondStateWhenSkipConnectingProfiles(
+ CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE)
+ .setMoreEventLogForQuality(MORE_LOG_FOR_QUALITY)
+ .setRetryGattConnectionAndSecretHandshake(
+ RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE)
+ .setGattConnectShortTimeoutMs(GATT_CONNECT_SHORT_TIMEOUT_MS)
+ .setGattConnectLongTimeoutMs(GATT_CONNECTION_LONG_TIME_OUT_MS)
+ .setGattConnectShortTimeoutRetryMaxSpentTimeMs(
+ GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
+ .setAddressRotateRetryMaxSpentTimeMs(ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS)
+ .setPairingRetryDelayMs(PAIRING_RETRY_DELAY_MS)
+ .setSecretHandshakeShortTimeoutMs(HANDSHAKE_SHORT_TIMEOUT_MS)
+ .setSecretHandshakeLongTimeoutMs(HANDSHAKE_LONG_TIMEOUT_MS)
+ .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(
+ SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
+ .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(
+ SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
+ .setSecretHandshakeRetryAttempts(SECRET_HANDSHAKE_RETRY_ATTEMPTS)
+ .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(
+ SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS)
+ .setSignalLostRetryMaxSpentTimeMs(SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS)
+ .setGattConnectionAndSecretHandshakeNoRetryGattError(
+ getGattConnectionAndSecretHandshakeNoRetryGattError())
+ .setRetrySecretHandshakeTimeout(RETRY_SECRET_HANDSHAKE_TIMEOUT)
+ .setLogUserManualRetry(LOG_USER_MANUAL_RETRY)
+ .setEnablePairFlowShowUiWithoutProfileConnection(
+ ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION)
+ .setLogUserManualRetry(LOG_USER_MANUAL_CITY)
+ .setLogPairWithCachedModelId(LOG_PAIR_WITH_CACHED_MODEL_ID)
+ .setDirectConnectProfileIfModelIdInCache(
+ DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE);
+ }
+
+ private static ImmutableSet<Integer> getGattConnectionAndSecretHandshakeNoRetryGattError() {
+ ImmutableSet.Builder<Integer> noRetryGattErrorsBuilder = ImmutableSet.builder();
+ // When GATT connection fail we will not retry on error code 257
+ for (String errorCode :
+ Splitter.on(",").split("257,")) {
+ if (!TextUtils.isDigitsOnly(errorCode)) {
+ continue;
+ }
+
+ try {
+ noRetryGattErrorsBuilder.add(Integer.parseInt(errorCode));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ return noRetryGattErrorsBuilder.build();
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
new file mode 100644
index 0000000..f2b0d59
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair;
+
+import com.android.server.nearby.common.fastpair.service.UserActionHandlerBase;
+
+/**
+ * User action handler class.
+ */
+public class UserActionHandler extends UserActionHandlerBase {
+
+ public static final String EXTRA_DISCOVERY_ITEM = PREFIX + "EXTRA_DISCOVERY_ITEM";
+ public static final String EXTRA_FAST_PAIR_SECRET = PREFIX + "EXTRA_FAST_PAIR_SECRET";
+}
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
new file mode 100644
index 0000000..4f279a5
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.cache;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
+import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_FAST_PAIR_SECRET;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.server.nearby.common.ble.util.RangingUtils;
+import com.android.server.nearby.common.fastpair.IconUtils;
+import com.android.server.nearby.common.locator.Locator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.URISyntaxException;
+import java.time.Clock;
+import java.util.Objects;
+
+import service.proto.Cache;
+
+/**
+ * Wrapper class around StoredDiscoveryItem. A centralized place for methods related to
+ * updating/parsing StoredDiscoveryItem.
+ */
+public class DiscoveryItem implements Comparable<DiscoveryItem> {
+
+ 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;
+ private static final int APP_INSTALL_EXPIRATION_MILLIS = 600000;
+ private static final int ITEM_DELETABLE_MILLIS = 15000;
+
+ private final FastPairCacheManager mFastPairCacheManager;
+ private final Clock mClock;
+
+ private Cache.StoredDiscoveryItem mStoredDiscoveryItem;
+
+ /** IntDef for StoredDiscoveryItem.State */
+ @IntDef({
+ Cache.StoredDiscoveryItem.State.STATE_ENABLED_VALUE,
+ Cache.StoredDiscoveryItem.State.STATE_MUTED_VALUE,
+ Cache.StoredDiscoveryItem.State.STATE_DISABLED_BY_SYSTEM_VALUE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ItemState {}
+
+ public DiscoveryItem(Context context, Cache.StoredDiscoveryItem mStoredDiscoveryItem) {
+ this.mFastPairCacheManager = Locator.get(context, FastPairCacheManager.class);
+ this.mClock = Locator.get(context, Clock.class);
+ this.mStoredDiscoveryItem = mStoredDiscoveryItem;
+ }
+
+ /** @return A new StoredDiscoveryItem with state fields set to their defaults. */
+ public static Cache.StoredDiscoveryItem newStoredDiscoveryItem() {
+ Cache.StoredDiscoveryItem.Builder storedDiscoveryItem =
+ Cache.StoredDiscoveryItem.newBuilder();
+ storedDiscoveryItem.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+ return storedDiscoveryItem.build();
+ }
+
+ /** @return True if the item is currently nearby. */
+ public boolean isNearby() {
+ return isNearby(mStoredDiscoveryItem, mClock.millis());
+ }
+
+ /**
+ * Checks if the item is Nearby
+ */
+ static boolean isNearby(Cache.StoredDiscoveryItem item, long currentTimeMillis) {
+ // A notification may disappear early, if we get a lost callback from Messages.
+ // But regardless, if we haven't detected the thing in Xmin, consider it gone.
+ return !isLost(item)
+ && !isExpired(
+ currentTimeMillis,
+ item.getLastObservationTimestampMillis());
+ }
+
+ /**
+ * Checks if store discovery item support fast pair or not.
+ */
+ public boolean isFastPair() {
+ Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl());
+ if (intent == null) {
+ Log.w("FastPairDiscovery", "FastPair: fail to parse action url"
+ + mStoredDiscoveryItem.getActionUrl());
+ return false;
+ }
+ return ACTION_FAST_PAIR.equals(intent.getAction());
+ }
+
+ /**
+ * Sets the pairing result to done.
+ */
+ public void setPairingProcessDone(boolean success) {
+ setLastUserExperience(success ? Cache.StoredDiscoveryItem.ExperienceType.EXPERIENCE_GOOD
+ : Cache.StoredDiscoveryItem.ExperienceType.EXPERIENCE_BAD);
+ setLostMillis(mClock.millis());
+ Log.d("FastPairDiscovery",
+ "FastPair: set Lost when pairing process done, " + getId());
+ }
+
+ /**
+ * Sets the store discovery item lost time.
+ */
+ public void setLostMillis(long lostMillis) {
+ mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().setLostMillis(lostMillis).build();
+
+ mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().clearRssi().build();
+
+ mFastPairCacheManager.saveDiscoveryItem(this);
+ }
+
+ /**
+ * Sets the store discovery item mac address.
+ */
+ public void setMacAddress(String address) {
+ mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().setMacAddress(address).build();
+
+ mFastPairCacheManager.saveDiscoveryItem(this);
+ }
+
+ /**
+ * Sets the user experience to be good or bad.
+ */
+ public void setLastUserExperience(Cache.StoredDiscoveryItem.ExperienceType experienceType) {
+ mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder()
+ .setLastUserExperience(experienceType).build();
+ mFastPairCacheManager.saveDiscoveryItem(this);
+ }
+ /**
+ * Gets the user experience to be good or bad.
+ */
+ public Cache.StoredDiscoveryItem.ExperienceType getLastUserExperience() {
+ return mStoredDiscoveryItem.getLastUserExperience();
+ }
+
+ /**
+ * Checks if the item is lost.
+ */
+ private static boolean isLost(Cache.StoredDiscoveryItem item) {
+ return item.getLastObservationTimestampMillis() <= item.getLostMillis();
+ }
+
+ /**
+ * Checks if the item is expired. Expired items are those over getItemExpirationMillis() eg. 2
+ * minutes
+ */
+ public static boolean isExpired(
+ long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) {
+ if (lastObservationTimestampMillis == null) {
+ return true;
+ }
+ return (currentTimestampMillis - lastObservationTimestampMillis)
+ >= ITEM_EXPIRATION_MILLIS;
+ }
+
+ /**
+ * Checks if the item is deletable for saving disk space. Deletable items are those over
+ * getItemDeletableMillis eg. over 25 hrs.
+ */
+ public static boolean isDeletable(
+ long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) {
+ if (lastObservationTimestampMillis == null) {
+ return true;
+ }
+ return currentTimestampMillis - lastObservationTimestampMillis
+ >= ITEM_DELETABLE_MILLIS;
+ }
+
+ /** Checks if the item has a pending app install */
+ public boolean isPendingAppInstallValid() {
+ return isPendingAppInstallValid(mClock.millis());
+ }
+
+ /**
+ * Checks if pending app valid.
+ */
+ public boolean isPendingAppInstallValid(long appInstallMillis) {
+ return isPendingAppInstallValid(appInstallMillis, mStoredDiscoveryItem);
+ }
+
+ /**
+ * Checks if the app install time expired.
+ */
+ public static boolean isPendingAppInstallValid(
+ long currentMillis, Cache.StoredDiscoveryItem storedItem) {
+ return currentMillis - storedItem.getPendingAppInstallTimestampMillis()
+ < APP_INSTALL_EXPIRATION_MILLIS;
+ }
+
+
+ /** Checks if the item has enough data to be shown */
+ public boolean isReadyForDisplay() {
+ if (isOffline()) {
+ return true;
+ }
+ boolean hasUrlOrPopularApp = !mStoredDiscoveryItem.getActionUrl().isEmpty();
+
+ return !TextUtils.isEmpty(mStoredDiscoveryItem.getTitle()) && hasUrlOrPopularApp;
+ }
+
+ /** Checks if the item has server error */
+ public boolean isDisabledByServer() {
+ return mStoredDiscoveryItem.getDebugCategory()
+ == Cache.StoredDiscoveryItem.DebugMessageCategory.STATUS_DISABLED_BY_SERVER;
+ }
+
+ private boolean isOffline() {
+ return isOfflineType(getType());
+ }
+
+ /** Checks if the item can be generated on client side. */
+ private static boolean isOfflineType(Cache.NearbyType type) {
+ return type == Cache.NearbyType.NEARBY_CHROMECAST || type == Cache.NearbyType.NEARBY_WEAR;
+ }
+
+ /** Checks if the action url is app install */
+ public boolean isApp() {
+ return mStoredDiscoveryItem.getActionUrlType() == Cache.ResolvedUrlType.APP;
+ }
+
+ /** Checks if it's device item. e.g. Chromecast / Wear */
+ public boolean isDevice() {
+ return isDeviceType(mStoredDiscoveryItem.getType());
+ }
+
+ /** Returns true if an item is muted, or if state is unavailable. */
+ public boolean isMuted() {
+ return mStoredDiscoveryItem.getState() != Cache.StoredDiscoveryItem.State.STATE_ENABLED;
+ }
+
+ /**
+ * Returns the state of store discovery item.
+ */
+ public Cache.StoredDiscoveryItem.State getState() {
+ return mStoredDiscoveryItem.getState();
+ }
+
+ /** Checks if it's device item. e.g. Chromecast / Wear */
+ public static boolean isDeviceType(Cache.NearbyType type) {
+ return type == Cache.NearbyType.NEARBY_CHROMECAST
+ || type == Cache.NearbyType.NEARBY_WEAR
+ || type == Cache.NearbyType.NEARBY_DEVICE;
+ }
+
+ /**
+ * Check if the type is supported.
+ */
+ public static boolean isTypeEnabled(Cache.NearbyType type) {
+ switch (type) {
+ case NEARBY_WEAR:
+ case NEARBY_CHROMECAST:
+ case NEARBY_DEVICE:
+ return true;
+ default:
+ Log.e("FastPairDiscoveryItem", "Invalid item type " + type.name());
+ return false;
+ }
+ }
+
+ /** Gets hash code of UI related data so we can collapse identical items. */
+ public int getUiHashCode() {
+ switch (mStoredDiscoveryItem.getType()) {
+ case NEARBY_CHROMECAST:
+ case NEARBY_WEAR:
+ // For the special-case device types, show one item per type.
+ return Objects.hashCode(mStoredDiscoveryItem.getType());
+ case NEARBY_DEVICE:
+ case NEARBY_TYPE_UNKNOWN:
+ default:
+ return Objects.hash(
+ mStoredDiscoveryItem.getType(),
+ mStoredDiscoveryItem.getTitle(),
+ mStoredDiscoveryItem.getDescription(),
+ mStoredDiscoveryItem.getAppName(),
+ mStoredDiscoveryItem.getDisplayUrl(),
+ mStoredDiscoveryItem.getMacAddress());
+ }
+ }
+
+ // Getters below
+ /**
+ * Returns the id of store discovery item.
+ */
+ @Nullable
+ public String getId() {
+ return mStoredDiscoveryItem.getId();
+ }
+
+ /**
+ * Returns the type of store discovery item.
+ */
+ @Nullable
+ public Cache.NearbyType getType() {
+ return mStoredDiscoveryItem.getType();
+ }
+
+ /**
+ * Returns the title of discovery item.
+ */
+ @Nullable
+ public String getTitle() {
+ return mStoredDiscoveryItem.getTitle();
+ }
+
+ /**
+ * Returns the description of discovery item.
+ */
+ @Nullable
+ public String getDescription() {
+ return mStoredDiscoveryItem.getDescription();
+ }
+
+ /**
+ * Returns the mac address of discovery item.
+ */
+ @Nullable
+ public String getMacAddress() {
+ return mStoredDiscoveryItem.getMacAddress();
+ }
+
+ /**
+ * Returns the display url of discovery item.
+ */
+ @Nullable
+ public String getDisplayUrl() {
+ return mStoredDiscoveryItem.getDisplayUrl();
+ }
+
+ /**
+ * Returns the public key of discovery item.
+ */
+ @Nullable
+ public byte[] getAuthenticationPublicKeySecp256R1() {
+ return mStoredDiscoveryItem.getAuthenticationPublicKeySecp256R1().toByteArray();
+ }
+
+ /**
+ * Returns the pairing secret.
+ */
+ @Nullable
+ public String getFastPairSecretKey() {
+ Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl());
+ if (intent == null) {
+ Log.d("FastPairDiscoveryItem", "FastPair: fail to parse action url "
+ + mStoredDiscoveryItem.getActionUrl());
+ return null;
+ }
+ return intent.getStringExtra(EXTRA_FAST_PAIR_SECRET);
+ }
+
+ /**
+ * Returns the fast pair info of discovery item.
+ */
+ @Nullable
+ public Cache.FastPairInformation getFastPairInformation() {
+ return mStoredDiscoveryItem.hasFastPairInformation()
+ ? mStoredDiscoveryItem.getFastPairInformation() : null;
+ }
+
+ /**
+ * Returns the app name of discovery item.
+ */
+ @Nullable
+ private String getAppName() {
+ return mStoredDiscoveryItem.getAppName();
+ }
+
+ /**
+ * Returns the package name of discovery item.
+ */
+ @Nullable
+ public String getAppPackageName() {
+ return mStoredDiscoveryItem.getPackageName();
+ }
+
+
+ /**
+ * Returns the feature graph url of discovery item.
+ */
+ @Nullable
+ private String getHeroImage() {
+ return mStoredDiscoveryItem.getFeatureGraphicUrl();
+ }
+
+ /**
+ * Returns the action url of discovery item.
+ */
+ @Nullable
+ public String getActionUrl() {
+ return mStoredDiscoveryItem.getActionUrl();
+ }
+
+ /**
+ * Returns the rssi value of discovery item.
+ */
+ @Nullable
+ public Integer getRssi() {
+ return mStoredDiscoveryItem.getRssi();
+ }
+
+ /**
+ * Returns the ble record of discovery item.
+ */
+ @Nullable
+ public byte[] getBleRecordBytes() {
+ return mStoredDiscoveryItem.getBleRecordBytes().toByteArray();
+ }
+
+ /**
+ * Returns the TX power of discovery item.
+ */
+ @Nullable
+ public Integer getTxPower() {
+ return mStoredDiscoveryItem.getTxPower();
+ }
+
+ /**
+ * Returns the first observed time stamp of discovery item.
+ */
+ @Nullable
+ public Long getFirstObservationTimestampMillis() {
+ return mStoredDiscoveryItem.getFirstObservationTimestampMillis();
+ }
+
+ /**
+ * Returns the last observed time stamp of discovery item.
+ */
+ @Nullable
+ public Long getLastObservationTimestampMillis() {
+ return mStoredDiscoveryItem.getLastObservationTimestampMillis();
+ }
+
+ /**
+ * Calculates an estimated distance for the item, computed from the TX power (at 1m) and RSSI.
+ *
+ * @return estimated distance, or null if there is no RSSI or no TX power.
+ */
+ @Nullable
+ public Double getEstimatedDistance() {
+ // In the future, we may want to do a foreground subscription to leverage onDistanceChanged.
+ return RangingUtils.distanceFromRssi(mStoredDiscoveryItem.getRssi(),
+ mStoredDiscoveryItem.getTxPower());
+ }
+
+ /**
+ * Gets icon Bitmap from icon store.
+ *
+ * @return null if no icon or icon size is incorrect.
+ */
+ @Nullable
+ public Bitmap getIcon() {
+ Bitmap icon =
+ BitmapFactory.decodeByteArray(
+ mStoredDiscoveryItem.getIconPng().toByteArray(),
+ 0 /* offset */, mStoredDiscoveryItem.getIconPng().size());
+ if (IconUtils.isIconSizeCorrect(icon)) {
+ return icon;
+ } else {
+ return null;
+ }
+ }
+
+ /** Gets a FIFE URL of the icon. */
+ @Nullable
+ public String getIconFifeUrl() {
+ return mStoredDiscoveryItem.getIconFifeUrl();
+ }
+
+ /**
+ * Gets group id of storedDiscoveryItem.
+ */
+ @Nullable
+ public String getGroupId() {
+ return mStoredDiscoveryItem.getGroupId();
+ }
+
+
+ /**
+ * Compares this object to the specified object: 1. By device type. Device setups are 'greater
+ * than' beacons. 2. By relevance. More relevant items are 'greater than' less relevant items.
+ * 3.By distance. Nearer items are 'greater than' further items.
+ *
+ * <p>In the list view, we sort in descending order, i.e. we put the most relevant items first.
+ */
+ @Override
+ public int compareTo(DiscoveryItem another) {
+ if (getType() != another.getType()) {
+ // For device type v.s. beacon type, rank device item higher.
+ return isDevice() ? 1 : -1;
+ }
+ // For items of the same relevance, compare distance.
+ Double distance1 = getEstimatedDistance();
+ Double distance2 = another.getEstimatedDistance();
+ distance1 = distance1 != null ? distance1 : Double.MAX_VALUE;
+ distance2 = distance2 != null ? distance2 : Double.MAX_VALUE;
+ // Negate because closer items are better ("greater than") further items.
+ return -distance1.compareTo(distance2);
+ }
+
+
+ public Integer getTriggerIdAttachmentTypeHash() {
+ return Objects.hash(mStoredDiscoveryItem.getTriggerId(),
+ mStoredDiscoveryItem.getAttachmentType());
+ }
+
+ @Nullable
+ public String getTriggerId() {
+ return mStoredDiscoveryItem.getTriggerId();
+ }
+
+ @Override
+ public boolean equals(Object another) {
+ if (another instanceof DiscoveryItem) {
+ return ((DiscoveryItem) another).mStoredDiscoveryItem.equals(mStoredDiscoveryItem);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mStoredDiscoveryItem.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "[type=%s], [triggerId=%s], [id=%s], [title=%s], [url=%s], "
+ + "[ready=%s], [macAddress=%s]",
+ getType().name(),
+ getTriggerId(),
+ getId(),
+ getTitle(),
+ getActionUrl(),
+ isReadyForDisplay(),
+ maskBluetoothAddress(getMacAddress()));
+ }
+
+ /**
+ * Gets a copy of the StoredDiscoveryItem proto backing this DiscoveryItem. Currently needed for
+ * Fast Pair 2.0: We store the item in the cloud associated with a user's account, to enable
+ * pairing with other devices owned by the user.
+ */
+ public Cache.StoredDiscoveryItem getCopyOfStoredItem() {
+ return mStoredDiscoveryItem;
+ }
+
+ /**
+ * Gets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate
+ * values that production code should not manipulate.
+ */
+
+ public Cache.StoredDiscoveryItem getStoredItemForTest() {
+ return mStoredDiscoveryItem;
+ }
+
+ /**
+ * Sets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate
+ * values that production code should not manipulate.
+ */
+ public void setStoredItemForTest(Cache.StoredDiscoveryItem s) {
+ mStoredDiscoveryItem = s;
+ }
+
+ /**
+ * Parse the intent from item url.
+ */
+ public static Intent parseIntentScheme(String uri) {
+ try {
+ return Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
index 7955f2a..0c3461d 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
@@ -31,19 +31,16 @@
* Save FastPair device info to database to avoid multiple requesting.
*/
public class FastPairCacheManager {
-
- private static final String FAST_PAIR_SERVER_CACHE = "FAST_PAIR_SERVER_CACHE";
+ private final Context mContext;
public FastPairCacheManager(Context context) {
-
+ mContext = context;
}
/**
* Saves the response to the db
*/
- private void saveDevice() {
-
- }
+ private void saveDevice() {}
Cache.ServerResponseDbItem getDeviceFromScanResult(ScanResult scanResult) {
return Cache.ServerResponseDbItem.newBuilder().build();
@@ -59,12 +56,33 @@
return true;
}
+ /**
+ * Save discovery item into database.
+ */
+ public boolean saveDiscoveryItem(DiscoveryItem item) {
+ return true;
+ }
+
@Annotations.EventThread
private Rpcs.GetObservedDeviceResponse getObservedDeviceInfo(ScanResult scanResult) {
return Rpcs.GetObservedDeviceResponse.getDefaultInstance();
}
/**
+ * Get discovery item from item id.
+ */
+ public DiscoveryItem getDiscoveryItem(String itemId) {
+ return new DiscoveryItem(mContext, Cache.StoredDiscoveryItem.getDefaultInstance());
+ }
+
+ /**
+ * Get scan result from local database use model id
+ */
+ public Cache.StoredScanResult getStoredScanResult(String modelId) {
+ return Cache.StoredScanResult.getDefaultInstance();
+ }
+
+ /**
* Test function to verify FastPairCacheManager setup.
*/
public void printLog() {
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java
new file mode 100644
index 0000000..68217c1
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.footprint;
+
+/**
+ * FootprintDeviceManager is responsible for all of the foot print operation. Footprint will
+ * store all of device info that already paired with certain account. This class will call AOSP
+ * api to let OEM save certain device.
+ */
+public class FootprintsDeviceManager {
+}
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
new file mode 100644
index 0000000..d59305b
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.halfsheet;
+
+import android.bluetooth.BluetoothDevice;
+
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+/**
+ * Fast Pair ux manager for half sheet.
+ */
+public class FastPairHalfSheetManager {
+
+ /**
+ * Shows pairing fail half sheet.
+ */
+ public void showPairingFailed() {
+
+ }
+
+ /**
+ * Get the half sheet status whether it is foreground or dismissed
+ */
+ public boolean getHalfSheetForegroundState() {
+ return true;
+ }
+
+ /**
+ * Show passkey confirmation info on half sheet
+ */
+ public void showPasskeyConfirmation(BluetoothDevice device, int passkey) {}
+
+ /**
+ * This function will handle pairing steps for half sheet.
+ */
+ public void showPairingHalfSheet(DiscoveryItem item) {}
+
+ /**
+ * Shows pairing success info.
+ */
+ public void showPairingSuccessHalfSheet(String address){}
+
+ /**
+ * Removes dismiss runnable.
+ */
+ public void disableDismissRunnable(){}
+
+ /**
+ * Destroys the bluetooth pairing controller.
+ */
+ public void destroyBluetoothPairController(){}
+
+ /**
+ * Notify manager the pairing has finished.
+ */
+ public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {}
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
new file mode 100644
index 0000000..b1ae573
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.notification;
+
+
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+/**
+ * Responsible for show notification logic.
+ */
+public class FastPairNotificationManager {
+
+ /**
+ * FastPair notification manager that handle notification ui for fast pair.
+ */
+ public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon,
+ int notificationId) {
+ }
+ /**
+ * FastPair notification manager that handle notification ui for fast pair.
+ */
+ public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon) {
+
+ }
+
+ /**
+ * Shows pairing in progress notification.
+ */
+ public void showConnectingNotification() {}
+
+ /**
+ * Shows success notification
+ */
+ public void showPairingSucceededNotification(
+ @Nullable String companionApp,
+ int batteryLevel,
+ @Nullable String deviceName,
+ String address) {
+
+ }
+
+ /**
+ * Shows failed notification.
+ */
+ public void showPairingFailedNotification(byte[] accountKey) {
+
+ }
+
+ /**
+ * Notify the pairing process is done.
+ */
+ public void notifyPairingProcessDone(boolean success, boolean forceNotify,
+ String privateAddress, String publicAddress) {}
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
new file mode 100644
index 0000000..c95f74f
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.pairinghandler;
+
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.intdefs.NearbyEventIntDefs;
+
+/** Pairing progress handler that handle pairing come from half sheet. */
+public final class HalfSheetPairingProgressHandler extends PairingProgressHandlerBase {
+
+ private final FastPairHalfSheetManager mFastPairHalfSheetManager;
+ private final boolean mIsSubsequentPair;
+ private final DiscoveryItem mItemResurface;
+
+ HalfSheetPairingProgressHandler(
+ Context context,
+ DiscoveryItem item,
+ @Nullable String companionApp,
+ @Nullable byte[] accountKey) {
+ super(context, item);
+ this.mFastPairHalfSheetManager = Locator.get(context, FastPairHalfSheetManager.class);
+ this.mIsSubsequentPair =
+ item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null;
+ this.mItemResurface = item;
+ }
+
+ @Override
+ protected int getPairStartEventCode() {
+ return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START
+ : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START;
+ }
+
+ @Override
+ protected int getPairEndEventCode() {
+ return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END
+ : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END;
+ }
+
+ @Override
+ public void onPairingStarted() {
+ super.onPairingStarted();
+ // Half sheet is not in the foreground reshow half sheet, also avoid showing HalfSheet on TV
+ if (!mFastPairHalfSheetManager.getHalfSheetForegroundState()) {
+ mFastPairHalfSheetManager.showPairingHalfSheet(mItemResurface);
+ }
+ mFastPairHalfSheetManager.disableDismissRunnable();
+ }
+
+ @Override
+ public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) {
+ super.onHandlePasskeyConfirmation(device, passkey);
+ mFastPairHalfSheetManager.showPasskeyConfirmation(device, passkey);
+ }
+
+ @Nullable
+ @Override
+ public String onPairedCallbackCalled(
+ FastPairConnection connection,
+ byte[] accountKey,
+ FootprintsDeviceManager footprints,
+ String address) {
+ String deviceName = super.onPairedCallbackCalled(connection, accountKey,
+ footprints, address);
+ mFastPairHalfSheetManager.showPairingSuccessHalfSheet(address);
+ mFastPairHalfSheetManager.disableDismissRunnable();
+ return deviceName;
+ }
+
+ @Override
+ public void onPairingFailed(Throwable throwable) {
+ super.onPairingFailed(throwable);
+ mFastPairHalfSheetManager.disableDismissRunnable();
+ mFastPairHalfSheetManager.showPairingFailed();
+ mFastPairHalfSheetManager.notifyPairingProcessDone(
+ /* success= */ false, /* publicAddress= */ null, mItem);
+ // fix auto rebond issue
+ mFastPairHalfSheetManager.destroyBluetoothPairController();
+ }
+
+ @Override
+ public void onPairingSuccess(String address) {
+ super.onPairingSuccess(address);
+ mFastPairHalfSheetManager.disableDismissRunnable();
+ mFastPairHalfSheetManager
+ .notifyPairingProcessDone(/* success= */ true, address, mItem);
+ mFastPairHalfSheetManager.destroyBluetoothPairController();
+ }
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
new file mode 100644
index 0000000..d469c45
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.pairinghandler;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.intdefs.NearbyEventIntDefs;
+
+/** Pairing progress handler for pairing coming from notifications. */
+@SuppressWarnings("nullness")
+public class NotificationPairingProgressHandler extends PairingProgressHandlerBase {
+ private final FastPairNotificationManager mFastPairNotificationManager;
+ @Nullable
+ private final String mCompanionApp;
+ @Nullable
+ private final byte[] mAccountKey;
+ private final boolean mIsSubsequentPair;
+
+ NotificationPairingProgressHandler(
+ Context context,
+ DiscoveryItem item,
+ @Nullable String companionApp,
+ @Nullable byte[] accountKey,
+ FastPairNotificationManager mFastPairNotificationManager) {
+ super(context, item);
+ this.mFastPairNotificationManager = mFastPairNotificationManager;
+ this.mCompanionApp = companionApp;
+ this.mAccountKey = accountKey;
+ this.mIsSubsequentPair =
+ item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null;
+ }
+
+ @Override
+ public int getPairStartEventCode() {
+ return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START
+ : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START;
+ }
+
+ @Override
+ public int getPairEndEventCode() {
+ return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END
+ : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END;
+ }
+
+ @Override
+ public void onReadyToPair() {
+ super.onReadyToPair();
+ mFastPairNotificationManager.showConnectingNotification();
+ }
+
+ @Override
+ public String onPairedCallbackCalled(
+ FastPairConnection connection,
+ byte[] accountKey,
+ FootprintsDeviceManager footprints,
+ String address) {
+ String deviceName = super.onPairedCallbackCalled(connection, accountKey, footprints,
+ address);
+
+ int batteryLevel = -1;
+
+ BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+ BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
+ if (bluetoothAdapter != null) {
+ // Need to check battery level here set that to -1 for now
+ batteryLevel = -1;
+ } else {
+ Log.v(
+ "NotificationPairingProgressHandler",
+ "onPairedCallbackCalled getBatteryLevel failed,"
+ + " adapter is null");
+ }
+ mFastPairNotificationManager.showPairingSucceededNotification(
+ !TextUtils.isEmpty(mCompanionApp) ? mCompanionApp : null,
+ batteryLevel,
+ deviceName,
+ address);
+ return deviceName;
+ }
+
+ @Override
+ public void onPairingFailed(Throwable throwable) {
+ super.onPairingFailed(throwable);
+ mFastPairNotificationManager.showPairingFailedNotification(mAccountKey);
+ mFastPairNotificationManager.notifyPairingProcessDone(
+ /* success= */ false,
+ /* forceNotify= */ false,
+ /* privateAddress= */ mItem.getMacAddress(),
+ /* publicAddress= */ null);
+ }
+
+ @Override
+ public void onPairingSuccess(String address) {
+ super.onPairingSuccess(address);
+ mFastPairNotificationManager.notifyPairingProcessDone(
+ /* success= */ true,
+ /* forceNotify= */ false,
+ /* privateAddress= */ mItem.getMacAddress(),
+ /* publicAddress= */ address);
+ }
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
new file mode 100644
index 0000000..760b4e0
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.pairinghandler;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
+import static com.android.server.nearby.fastpair.FastPairManager.isThroughFastPair2InitialPairing;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.intdefs.FastPairEventIntDefs;
+
+/** Base class for pairing progress handler. */
+public abstract class PairingProgressHandlerBase {
+ protected final Context mContext;
+ protected final DiscoveryItem mItem;
+ @Nullable
+ private FastPairEventIntDefs.ErrorCode mRescueFromError;
+
+ protected abstract int getPairStartEventCode();
+
+ protected abstract int getPairEndEventCode();
+
+ protected PairingProgressHandlerBase(Context context, DiscoveryItem item) {
+ this.mContext = context;
+ this.mItem = item;
+ }
+
+
+ /**
+ * Pairing progress init function.
+ */
+ public static PairingProgressHandlerBase create(
+ Context context,
+ DiscoveryItem item,
+ @Nullable String companionApp,
+ @Nullable byte[] accountKey,
+ FootprintsDeviceManager footprints,
+ FastPairNotificationManager notificationManager,
+ FastPairHalfSheetManager fastPairHalfSheetManager,
+ boolean isRetroactivePair) {
+ PairingProgressHandlerBase pairingProgressHandlerBase;
+ // Disable half sheet on subsequent pairing
+ if (item.getAuthenticationPublicKeySecp256R1() != null
+ && accountKey != null) {
+ // Subsequent pairing
+ pairingProgressHandlerBase =
+ new NotificationPairingProgressHandler(
+ context, item, companionApp, accountKey, notificationManager);
+ } else {
+ pairingProgressHandlerBase =
+ new HalfSheetPairingProgressHandler(context, item, companionApp, accountKey);
+ }
+
+
+ Log.v("PairingHandler",
+ "PairingProgressHandler:Create %s for pairing");
+ return pairingProgressHandlerBase;
+ }
+
+
+ /**
+ * Function calls when pairing start.
+ */
+ public void onPairingStarted() {
+ Log.v("PairingHandler", "PairingProgressHandler:onPairingStarted");
+ }
+
+ /**
+ * Waits for screen to unlock.
+ */
+ public void onWaitForScreenUnlock() {
+ Log.v("PairingHandler", "PairingProgressHandler:onWaitForScreenUnlock");
+ }
+
+ /**
+ * Function calls when screen unlock.
+ */
+ public void onScreenUnlocked() {
+ Log.v("PairingHandler", "PairingProgressHandler:onScreenUnlocked");
+ }
+
+ /**
+ * Calls when the handler is ready to pair.
+ */
+ public void onReadyToPair() {
+ Log.v("PairingHandler", "PairingProgressHandler:onReadyToPair");
+ }
+
+ /**
+ * Helps to set up pairing preference.
+ */
+ public void onSetupPreferencesBuilder(Preferences.Builder builder) {
+ Log.v("PairingHandler", "PairingProgressHandler:onSetupPreferencesBuilder");
+ }
+
+ /**
+ * Calls when pairing setup complete.
+ */
+ public void onPairingSetupCompleted() {
+ Log.v("PairingHandler", "PairingProgressHandler:onPairingSetupCompleted");
+ }
+
+ /** Called while pairing if needs to handle the passkey confirmation by Ui. */
+ public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) {
+ Log.v("PairingHandler", "PairingProgressHandler:onHandlePasskeyConfirmation");
+ }
+
+ /**
+ * In this callback, we know if it is a real initial pairing by existing account key, and do
+ * following things:
+ * <li>1, optIn footprint for initial pairing.
+ * <li>2, write the device name to provider
+ * <li>2.1, generate default personalized name for initial pairing or get the personalized name
+ * from footprint for subsequent pairing.
+ * <li>2.2, set alias name for the bluetooth device.
+ * <li>2.3, update the device name for connection to write into provider for initial pair.
+ * <li>3, suppress battery notifications until oobe finishes.
+ *
+ * @return display name of the pairing device
+ */
+ @Nullable
+ public String onPairedCallbackCalled(
+ FastPairConnection connection,
+ byte[] accountKey,
+ FootprintsDeviceManager footprints,
+ String address) {
+ Log.v("PairingHandler",
+ "PairingProgressHandler:onPairedCallbackCalled with address: "
+ + address);
+
+ byte[] existingAccountKey = connection.getExistingAccountKey();
+ optInFootprintsForInitialPairing(footprints, mItem, accountKey, existingAccountKey);
+ // Add support for naming the device
+ return null;
+ }
+
+ /**
+ * Gets the related info from db use account key.
+ */
+ @Nullable
+ public byte[] getKeyForLocalCache(
+ byte[] accountKey, FastPairConnection connection,
+ FastPairConnection.SharedSecret sharedSecret) {
+ Log.v("PairingHandler", "PairingProgressHandler:getKeyForLocalCache");
+ return accountKey != null ? accountKey : connection.getExistingAccountKey();
+ }
+
+ /**
+ * Function handles pairing fail.
+ */
+ public void onPairingFailed(Throwable throwable) {
+ Log.w("PairingHandler", "PairingProgressHandler:onPairingFailed");
+ }
+
+ /**
+ * Function handles pairing success.
+ */
+ public void onPairingSuccess(String address) {
+ Log.v("PairingHandler", "PairingProgressHandler:onPairingSuccess with address: "
+ + maskBluetoothAddress(address));
+ }
+
+ private static void optInFootprintsForInitialPairing(
+ FootprintsDeviceManager footprints,
+ DiscoveryItem item,
+ byte[] accountKey,
+ @Nullable byte[] existingAccountKey) {
+ if (isThroughFastPair2InitialPairing(item, accountKey) && existingAccountKey == null) {
+ // enable the save to footprint
+ Log.v("PairingHandler", "footprint should call opt in here");
+ }
+ }
+
+ /**
+ * Returns {@code true} if the PairingProgressHandler is running at the background.
+ *
+ * <p>In order to keep the following status notification shows as a heads up, we must wait for
+ * the screen unlocked to continue.
+ */
+ public boolean skipWaitingScreenUnlock() {
+ return false;
+ }
+}
+
diff --git a/nearby/service/proto/src/fastpair/rpcs.proto b/nearby/service/proto/src/fastpair/rpcs.proto
index b684b48..384d471 100644
--- a/nearby/service/proto/src/fastpair/rpcs.proto
+++ b/nearby/service/proto/src/fastpair/rpcs.proto
@@ -245,7 +245,7 @@
string initial_notification_description_no_account = 3;
// The notification description for once we have finished pairing and the
- // companion app has been opened. For Bisto devices, this string will point
+ // companion app has been opened. For google assistant devices, this string will point
// users to setting up the assistant.
string open_companion_app_description = 4;