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;