Merge "Add deviceshadower and robotest for BluetoothClassicPairer."
diff --git a/nearby/apex/Android.bp b/nearby/apex/Android.bp
index 540be44..b277ac4 100644
--- a/nearby/apex/Android.bp
+++ b/nearby/apex/Android.bp
@@ -40,6 +40,7 @@
     java_libs: [
         "service-nearby",
     ],
+    apps: ["HalfSheetUX"],
 }
 
 filegroup {
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index ba97112..74980c9 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -65,7 +65,8 @@
     installable: false,
     visibility: [
         "//packages/modules/Nearby/service",
-        "//packages/modules/Nearby/tests",
+        "//packages/modules/Nearby/halfsheet",
+        "//packages/modules/Nearby/tests:__subpackages__",
     ],
 }
 
diff --git a/nearby/framework/api/system-current.txt b/nearby/framework/api/system-current.txt
index fe3cd98..9d0d135 100644
--- a/nearby/framework/api/system-current.txt
+++ b/nearby/framework/api/system-current.txt
@@ -33,5 +33,79 @@
     method @Nullable public byte[] getModelId();
   }
 
+  public abstract class NearbyDevice {
+    method public int getMedium();
+    method @IntRange(from=0xffffff81, to=126) public int getRssi();
+    method public static boolean isValidMedium(int);
+  }
+
+  public final class NearbyDeviceParcelable implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getBluetoothAddress();
+    method @Nullable public byte[] getData();
+    method @Nullable public String getFastPairModelId();
+    method public int getMedium();
+    method @Nullable public String getName();
+    method @IntRange(from=0xffffff81, to=126) public int getRssi();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.NearbyDeviceParcelable> CREATOR;
+  }
+
+  public static final class NearbyDeviceParcelable.Builder {
+    ctor public NearbyDeviceParcelable.Builder();
+    method @NonNull public android.nearby.NearbyDeviceParcelable build();
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setBluetoothAddress(@Nullable String);
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setData(@Nullable byte[]);
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setFastPairModelId(@Nullable String);
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setMedium(int);
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setName(@Nullable String);
+    method @NonNull public android.nearby.NearbyDeviceParcelable.Builder setRssi(int);
+  }
+
+  public final class NearbyFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
+  public class NearbyManager {
+    method public void startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
+    method public void stopScan(@NonNull android.nearby.ScanCallback);
+  }
+
+  public interface ScanCallback {
+    method public void onDiscovered(@NonNull android.nearby.NearbyDevice);
+    method public void onLost(@NonNull android.nearby.NearbyDevice);
+    method public void onUpdated(@NonNull android.nearby.NearbyDevice);
+  }
+
+  public final class ScanRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getScanMode();
+    method public int getScanType();
+    method @NonNull public android.os.WorkSource getWorkSource();
+    method public boolean isEnableBle();
+    method public static boolean isValidScanMode(int);
+    method public static boolean isValidScanType(int);
+    method @NonNull public static String scanModeToString(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.ScanRequest> CREATOR;
+    field public static final int SCAN_MODE_BALANCED = 1; // 0x1
+    field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
+    field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
+    field public static final int SCAN_MODE_NO_POWER = -1; // 0xffffffff
+    field public static final int SCAN_TYPE_EXPOSURE_NOTIFICATION = 4; // 0x4
+    field public static final int SCAN_TYPE_FAST_PAIR = 1; // 0x1
+    field public static final int SCAN_TYPE_NEARBY_PRESENCE = 3; // 0x3
+    field public static final int SCAN_TYPE_NEARBY_SHARE = 2; // 0x2
+  }
+
+  public static final class ScanRequest.Builder {
+    ctor public ScanRequest.Builder();
+    method @NonNull public android.nearby.ScanRequest build();
+    method @NonNull public android.nearby.ScanRequest.Builder setEnableBle(boolean);
+    method @NonNull public android.nearby.ScanRequest.Builder setScanMode(int);
+    method @NonNull public android.nearby.ScanRequest.Builder setScanType(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.nearby.ScanRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
+  }
+
 }
 
diff --git a/nearby/framework/java/android/nearby/FastPairDevice.java b/nearby/framework/java/android/nearby/FastPairDevice.java
index bda1d48..1e766a5 100644
--- a/nearby/framework/java/android/nearby/FastPairDevice.java
+++ b/nearby/framework/java/android/nearby/FastPairDevice.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -16,7 +16,7 @@
 
 package android.nearby;
 
-import android.annotation.Hide;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -31,6 +31,9 @@
  * @hide
  */
 public class FastPairDevice extends NearbyDevice implements Parcelable {
+    /**
+     * Used to read a FastPairDevice from a Parcel.
+     */
     public static final Creator<FastPairDevice> CREATOR = new Creator<FastPairDevice>() {
         @Override
         public FastPairDevice createFromParcel(Parcel in) {
@@ -68,6 +71,17 @@
     @Nullable
     private final byte[] mData;
 
+    /**
+     * Creates a new FastPairDevice.
+     *
+     * @param name Name of the FastPairDevice. Can be {@code null} if there is no name.
+     * @param medium The {@link Medium} over which the device is discovered.
+     * @param rssi The received signal strength in dBm.
+     * @param modelId The identifier of the Fast Pair device.
+     *                Can be {@code null} if there is no Model ID.
+     * @param bluetoothAddress The hardware address of this BluetoothDevice.
+     * @param data Extra data for a Fast Pair device.
+     */
     public FastPairDevice(@Nullable String name,
             @Medium int medium,
             int rssi,
@@ -80,44 +94,69 @@
         this.mData = data;
     }
 
+    /**
+     * Gets the name of the device, or {@code null} if not available.
+     *
+     * @hide
+     */
     @Nullable
     @Override
     public String getName() {
         return mName;
     }
 
+    /** Gets the medium over which this device was discovered. */
     @Override
     public int getMedium() {
         return mMedium;
     }
 
+    /**
+     * Gets the received signal strength in dBm.
+     */
+    @IntRange(from = -127, to  = 126)
     @Override
     public int getRssi() {
         return mRssi;
     }
 
+    /**
+     * Gets the identifier of the Fast Pair device. Can be {@code null} if there is no Model ID.
+     */
     @Nullable
     public String getModelId() {
         return this.mModelId;
     }
 
+    /**
+     * Gets the hardware address of this BluetoothDevice.
+     */
     @NonNull
     public String getBluetoothAddress() {
         return mBluetoothAddress;
     }
 
-    // Only visible to system clients.
+    /**
+     * Gets the extra data for a Fast Pair device. Can be {@code null} if there is extra data.
+     *
+     * @hide
+     */
     @Nullable
-    @Hide
     public byte[] getData() {
         return mData;
     }
 
+    /**
+     * No special parcel contents.
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * Returns a string representation of this FastPairDevice.
+     */
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder();
@@ -178,7 +217,7 @@
      *
      * @hide
      */
-    public static final class Builder extends NearbyDevice.Builder {
+    public static final class Builder {
 
         @Nullable private String mName;
         @Medium private int mMedium;
@@ -188,6 +227,8 @@
         @Nullable private byte[] mData;
         /**
          * Sets the name of the Fast Pair device.
+         *
+         * @param name Name of the FastPairDevice. Can be {@code null} if there is no name.
          */
         @NonNull
         public Builder setName(@Nullable String name) {
@@ -197,6 +238,8 @@
 
         /**
          * Sets the medium over which the Fast Pair device is discovered.
+         *
+         * @param medium The {@link Medium} over which the device is discovered.
          */
         @NonNull
         public Builder setMedium(@Medium int medium) {
@@ -206,6 +249,8 @@
 
         /**
          * Sets the RSSI between the scan device and the discovered Fast Pair device.
+         *
+         * @param rssi The received signal strength in dBm.
          */
         @NonNull
         public Builder setRssi(int rssi) {
@@ -215,29 +260,35 @@
 
         /**
          * Sets the model Id of this Fast Pair device.
+         *
+         * @param modelId The identifier of the Fast Pair device. Can be {@code null}
+         *                if there is no Model ID.
          */
         @NonNull
-        public Builder setModelId(String modelId) {
+        public Builder setModelId(@Nullable String modelId) {
             mModelId = modelId;
             return this;
         }
 
         /**
          * Sets the hardware address of this BluetoothDevice.
+         *
+         * @param bluetoothAddress The hardware address of this BluetoothDevice.
          */
         @NonNull
-        public Builder setBluetoothAddress(@NonNull String maskedBluetoothAddress) {
-            mBluetoothAddress = maskedBluetoothAddress;
+        public Builder setBluetoothAddress(@NonNull String bluetoothAddress) {
+            Objects.requireNonNull(bluetoothAddress);
+            mBluetoothAddress = bluetoothAddress;
             return this;
         }
 
         /**
-         * Sets the raw data. Only visible to system API.
+         * Sets the raw data for a FastPairDevice. Can be {@code null} if there is no extra data.
+         *
          * @hide
          */
-        @Hide
         @NonNull
-        public Builder setData(byte[] data) {
+        public Builder setData(@Nullable byte[] data) {
             mData = data;
             return this;
         }
diff --git a/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl b/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
new file mode 100644
index 0000000..2e6fc87
--- /dev/null
+++ b/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
@@ -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 android.nearby;
+
+import android.content.Intent;
+/**
+  * Provides callback interface for halfsheet to send FastPair call back.
+  *
+  * {@hide}
+  */
+interface IFastPairHalfSheetCallback {
+     void onHalfSheetConnectionConfirm(in Intent intent);
+ }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
index 12c4ff1..790b2ed 100644
--- a/nearby/framework/java/android/nearby/NearbyDevice.java
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -17,7 +17,9 @@
 package android.nearby;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 
 import com.android.internal.util.Preconditions;
 
@@ -28,6 +30,7 @@
  *
  * @hide
  */
+@SystemApi
 public abstract class NearbyDevice {
 
     @Nullable
@@ -39,6 +42,11 @@
     final int mRssi;
 
     /**
+     * Creates a new NearbyDevice.
+     *
+     * @param name Local device name. Can be {@code null} if there is no name.
+     * @param medium The {@link Medium} over which the device is discovered.
+     * @param rssi The received signal strength in dBm.
      * @hide
      */
     public NearbyDevice(@Nullable String name, @Medium int medium, int rssi) {
@@ -63,6 +71,8 @@
 
     /**
      * True if the medium is defined in {@link Medium}.
+     *
+     * @param medium Integer that may represent a medium type.
      */
     public static boolean isValidMedium(@Medium int medium) {
         return medium == Medium.BLE
@@ -86,8 +96,9 @@
     }
 
     /**
-     * Returns the received signal strength in dBm. The valid range is [-127, 126].
+     * Returns the received signal strength in dBm.
      */
+    @IntRange(from = -127, to = 126)
     public int getRssi() {
         return mRssi;
     }
@@ -131,31 +142,5 @@
         int BLE = 1;
         int BLUETOOTH = 2;
     }
-
-    /**
-     * Builder for a NearbyDevice.
-     */
-    public abstract static class Builder {
-
-        /**
-         * Sets the name of Nearby Device.
-         */
-        public abstract Builder setName(String name);
-
-        /**
-         * Sets the medium over which the Nearby Device is discovered.
-         */
-        public abstract Builder setMedium(int medium);
-
-        /**
-         * Sets the RSSI between scanned device and the discovered device.
-         */
-        public abstract Builder setRssi(int rssi);
-
-        /**
-         * Builds the Nearby Device.
-         */
-        public abstract NearbyDevice build();
-    }
 }
 
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl
index e211187..1a88181 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.nearby;
 
 parcelable NearbyDeviceParcelable;
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 6910d13..ed3cb7b 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -17,8 +17,11 @@
 package android.nearby;
 
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.bluetooth.le.ScanRecord;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -34,7 +37,13 @@
  *
  * @hide
  */
+@SystemApi
 public final class NearbyDeviceParcelable implements Parcelable {
+
+    /**
+     * Used to read a NearbyDeviceParcelable from a Parcel.
+     */
+    @NonNull
     public static final Creator<NearbyDeviceParcelable> CREATOR =
             new Creator<NearbyDeviceParcelable>() {
                 @Override
@@ -76,7 +85,8 @@
     private final byte[] mData;
 
     private NearbyDeviceParcelable(@Nullable String name, int medium, int rssi,
-            @Nullable String fastPairModelId, @Nullable String bluetoothAddress, byte[] data) {
+            @Nullable String fastPairModelId, @Nullable String bluetoothAddress,
+            @Nullable byte[] data) {
         mName = name;
         mMedium = medium;
         mRssi = rssi;
@@ -85,11 +95,21 @@
         mData = data;
     }
 
+    /**
+     * No special parcel contents.
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * Flatten this NearbyDeviceParcelable in to a Parcel.
+     *
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     */
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mName == null ? 0 : 1);
@@ -113,28 +133,53 @@
         }
     }
 
+    /**
+     * Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name.
+     */
+    @Nullable
     public String getName() {
         return mName;
     }
 
+    /**
+     * Gets the {@link android.nearby.NearbyDevice.Medium} of the NearbyDeviceParcelable over which
+     * it is discovered.
+     */
+    @NearbyDevice.Medium
     public int getMedium() {
         return mMedium;
     }
 
+    /**
+     * Gets the received signal strength in dBm.
+     */
+    @IntRange(from = -127, to = 126)
     public int getRssi() {
         return mRssi;
     }
 
+    /**
+     * Gets the Fast Pair identifier. Returns {@code null} if there is no Model ID or this is not a
+     * Fast Pair device.
+     */
     @Nullable
     public String getFastPairModelId() {
         return mFastPairModelId;
     }
 
+    /**
+     * Gets the Bluetooth device hardware address. Returns {@code null} if the device is not
+     * discovered by Bluetooth.
+     */
     @Nullable
     public String getBluetoothAddress() {
         return mBluetoothAddress;
     }
 
+    /**
+     * Gets the raw data from the scanning. Returns {@code null} if there is no extra data.
+     */
+    @Nullable
     public byte[] getData() {
         return mData;
     }
@@ -157,48 +202,67 @@
 
         /**
          * Sets the name of the scanned device.
+         *
+         * @param name The local name of the scanned device.
          */
-        public Builder setName(String name) {
+        @NonNull
+        public Builder setName(@Nullable String name) {
             mName = name;
             return this;
         }
 
         /**
          * Sets the medium over which the device is discovered.
+         *
+         * @param medium The {@link NearbyDevice.Medium} over which the device is discovered.
          */
-        public Builder setMedium(int medium) {
+        @NonNull
+        public Builder setMedium(@NearbyDevice.Medium int medium) {
             mMedium = medium;
             return this;
         }
 
         /**
          * Sets the RSSI between scanned device and the discovered device.
+         *
+         * @param rssi The received signal strength in dBm.
          */
+        @NonNull
         public Builder setRssi(int rssi) {
             mRssi = rssi;
             return this;
         }
 
         /**
-         * Sets the identifier of the device.
+         * Sets the Fast Pair model Id.
+         *
+         * @param fastPairModelId Fast Pair device identifier.
          */
-        public Builder setFastPairModelId(String fastPairModelId) {
+        @NonNull
+        public Builder setFastPairModelId(@Nullable String fastPairModelId) {
             mFastPairModelId = fastPairModelId;
             return this;
         }
 
         /**
-         * Sets the scanned Fast Pair data.
+         * Sets the bluetooth address.
+         *
+         * @param bluetoothAddress The hardware address of the bluetooth device.
          */
-        public Builder setBluetoothAddress(String bluetoothAddress) {
+        @NonNull
+        public Builder setBluetoothAddress(@Nullable String bluetoothAddress) {
             mBluetoothAddress = bluetoothAddress;
             return this;
         }
 
         /**
-         * Sets the raw data.
+         * Sets the scanned raw data.
+         *
+         * @param data Data the scan.
+         * For example, {@link ScanRecord#getServiceData()} if scanned by Bluetooth.
          */
-        public Builder setData(byte[] data) {
+        @NonNull
+        public Builder setData(@Nullable byte[] data) {
             mData = data;
             return this;
         }
@@ -206,6 +270,7 @@
         /**
          * Builds a ScanResult.
          */
+        @NonNull
         public NearbyDeviceParcelable build() {
             return new NearbyDeviceParcelable(mName, mMedium, mRssi, mFastPairModelId,
                     mBluetoothAddress, mData);
diff --git a/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java b/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java
new file mode 100644
index 0000000..80d1005
--- /dev/null
+++ b/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.nearby;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for all Nearby services.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NearbyFrameworkInitializer {
+
+    private NearbyFrameworkInitializer() {}
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all
+     * Nearby services to {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides
+     * {@link SystemServiceRegistry}
+     */
+    public static void registerServiceWrappers() {
+        SystemServiceRegistry.registerContextAwareService(
+                Context.NEARBY_SERVICE,
+                NearbyManager.class,
+                (context, serviceBinder) -> {
+                    INearbyManager service = INearbyManager.Stub.asInterface(serviceBinder);
+                    return new NearbyManager(service);
+                }
+        );
+    }
+}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index d8f6917..4b69e4f 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -19,6 +19,10 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
 import android.os.RemoteException;
 
 import com.android.internal.annotations.GuardedBy;
@@ -38,6 +42,8 @@
  *
  * @hide
  */
+@SystemApi
+@SystemService(Context.NEARBY_SERVICE)
 public class NearbyManager {
 
     private static final String TAG = "NearbyManager";
@@ -46,7 +52,12 @@
             sScanListeners = new WeakHashMap<>();
     private final INearbyManager mService;
 
-    public NearbyManager(@NonNull INearbyManager service) {
+    /**
+     * Creates a new NearbyManager.
+     *
+     * @param service The service object.
+     */
+    NearbyManager(@NonNull INearbyManager service) {
         mService = service;
     }
 
@@ -69,10 +80,13 @@
      * Start scan for nearby devices with given parameters. Devices matching {@link ScanRequest}
      * will be delivered through the given callback.
      *
+     * @param scanRequest Various parameters clients send when requesting scanning.
      * @param executor Executor where the listener method is called.
+     * @param scanCallback The callback to notify clients when there is a scan result.
      */
-    public void startScan(ScanRequest scanRequest, ScanCallback scanCallback, @CallbackExecutor
-            Executor executor) {
+    public void startScan(@NonNull ScanRequest scanRequest,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull ScanCallback scanCallback) {
         Objects.requireNonNull(scanRequest, "scanRequest must not be null");
         Objects.requireNonNull(scanCallback, "scanCallback must not be null");
         Objects.requireNonNull(executor, "executor must not be null");
@@ -100,7 +114,13 @@
      * Stops the nearby device scan for the specified callback. The given callback
      * is guaranteed not to receive any invocations that happen after this method
      * is invoked.
+     *
+     * Suppressed lint: Registration methods should have overload that accepts delivery Executor.
+     * Already have executor in startScan() method.
+     *
+     * @param scanCallback  The callback that was used to start the scan.
      */
+    @SuppressLint("ExecutorRegistration")
     public void stopScan(@NonNull ScanCallback scanCallback) {
         Preconditions.checkArgument(scanCallback != null,
                 "invalid null scanCallback");
diff --git a/nearby/framework/java/android/nearby/ScanCallback.java b/nearby/framework/java/android/nearby/ScanCallback.java
index 498e672..1b1b4bc 100644
--- a/nearby/framework/java/android/nearby/ScanCallback.java
+++ b/nearby/framework/java/android/nearby/ScanCallback.java
@@ -17,6 +17,7 @@
 package android.nearby;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 
 /**
  * Reports newly discovered devices.
@@ -28,13 +29,26 @@
  *
  * @hide
  */
+@SystemApi
 public interface ScanCallback {
-    /** Reports a {@link NearbyDevice} being discovered. */
+    /**
+     * Reports a {@link NearbyDevice} being discovered.
+     *
+     * @param device {@link NearbyDevice} that is found.
+     */
     void onDiscovered(@NonNull NearbyDevice device);
 
-    /** Reports a {@link NearbyDevice} information(distance, packet, and etc) changed. */
+    /**
+     * Reports a {@link NearbyDevice} information(distance, packet, and etc) changed.
+     *
+     * @param device {@link NearbyDevice} that has updates.
+     */
     void onUpdated(@NonNull NearbyDevice device);
 
-    /** Reports a {@link NearbyDevice} is no longer within range. */
+    /**
+     * Reports a {@link NearbyDevice} is no longer within range.
+     *
+     * @param device {@link NearbyDevice} that is lost.
+     */
     void onLost(@NonNull NearbyDevice device);
 }
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index c9a4072..737f574 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.WorkSource;
@@ -36,6 +37,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class ScanRequest implements Parcelable {
 
     /** Scan type for scanning devices using fast pair protocol. */
@@ -60,6 +62,10 @@
      * results without starting BLE scans themselves.
      */
     public static final int SCAN_MODE_NO_POWER = -1;
+    /**
+     * Used to read a ScanRequest from a Parcel.
+     */
+    @NonNull
     public static final Creator<ScanRequest> CREATOR = new Creator<ScanRequest>() {
         @Override
         public ScanRequest createFromParcel(Parcel in) {
@@ -78,10 +84,10 @@
     private final @ScanType int mScanType;
     private final @ScanMode int mScanMode;
     private final boolean mEnableBle;
-    private final WorkSource mWorkSource;
+    private final @NonNull WorkSource mWorkSource;
 
     private ScanRequest(@ScanType int scanType, @ScanMode int scanMode, boolean enableBle,
-            WorkSource workSource) {
+            @NonNull WorkSource workSource) {
         mScanType = scanType;
         mScanMode = scanMode;
         mEnableBle = enableBle;
@@ -90,7 +96,10 @@
 
     /**
      * Convert scan mode to readable string.
+     *
+     * @param scanMode Integer that may represent a{@link ScanMode}.
      */
+    @NonNull
     public static String scanModeToString(@ScanMode int scanMode) {
         switch (scanMode) {
             case SCAN_MODE_LOW_LATENCY:
@@ -152,15 +161,23 @@
      *
      * @hide
      */
+    @SystemApi
+    @NonNull
     public WorkSource getWorkSource() {
         return mWorkSource;
     }
 
+    /**
+     * No special parcel contents.
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * Returns a string representation of this ScanRequest.
+     */
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder();
@@ -231,7 +248,10 @@
         /**
          * Sets the scan type for the request. The scan type must be one of the SCAN_TYPE_ constants
          * in {@link ScanRequest}.
+         *
+         * @param scanType The scan type for the request
          */
+        @NonNull
         public Builder setScanType(@ScanType int scanType) {
             mScanType = scanType;
             return this;
@@ -240,7 +260,10 @@
         /**
          * Sets the scan mode for the request. The scan type must be one of the SCAN_MODE_ constants
          * in {@link ScanRequest}.
+         *
+         * @param scanMode The scan mode for the request
          */
+        @NonNull
         public Builder setScanMode(@ScanMode int scanMode) {
             mScanMode = scanMode;
             return this;
@@ -248,8 +271,10 @@
 
         /**
          * Sets if the ble is enabled for scanning.
-         * in {@link ScanRequest}.
+         *
+         * @param enableBle If the BluetoothLe is enabled in the device.
          */
+        @NonNull
         public Builder setEnableBle(boolean enableBle) {
             mEnableBle = enableBle;
             return this;
@@ -263,10 +288,12 @@
          * <p>Permission enforcement occurs when the resulting scan request is used, not when
          * this method is invoked.
          *
+         * @param workSource identifying the application(s) for which to blame for the scan.
          * @hide
          */
         @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
         @NonNull
+        @SystemApi
         public Builder setWorkSource(@Nullable WorkSource workSource) {
             if (workSource == null) {
                 mWorkSource = new WorkSource();
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
new file mode 100644
index 0000000..781f308
--- /dev/null
+++ b/nearby/halfsheet/Android.bp
@@ -0,0 +1,58 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "HalfSheetDefaults",
+    srcs: ["src/**/*.java"],
+    sdk_version: "module_current",
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+    manifest: "AndroidManifest.xml",
+    plugins: ["java_api_finder"],
+    jarjar_rules: ":nearby-jarjar-rules",
+    libs: [
+        "framework-nearby-pre-jarjar",
+        "nearby-service-string",
+      ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.fragment_fragment",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.localbroadcastmanager_localbroadcastmanager",
+        "fast-pair-lite-protos",
+    ],
+    lint: { strict_updatability_linting: true }
+}
+
+android_app {
+    name: "HalfSheetUX",
+    defaults: ["HalfSheetDefaults"],
+    resource_dirs: ["res"],
+    certificate: ":com.android.nearby.halfsheetcertificate",
+    updatable: true,
+    static_libs: [
+        "com.google.android.material_material",
+    ],
+    apex_available: ["com.android.nearby",],
+}
+
+android_app_certificate {
+    name: "com.android.nearby.halfsheetcertificate",
+    certificate: "apk-certs/com.android.nearby.halfsheet"
+}
+
diff --git a/nearby/halfsheet/AndroidManifest.xml b/nearby/halfsheet/AndroidManifest.xml
new file mode 100644
index 0000000..3e8b39b
--- /dev/null
+++ b/nearby/halfsheet/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.nearby.halfsheet">
+    <application android:label="@string/app_name">
+        <activity
+            android:name="com.android.nearby.halfsheet.HalfSheetActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.nearby.SHOW_HALFSHEET"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
new file mode 100644
index 0000000..187d51e
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
Binary files differ
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
new file mode 100644
index 0000000..440c524
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF6zCCA9OgAwIBAgIUU5ATKevcNA5ZSurwgwGenwrr4c4wDQYJKoZIhvcNAQEL
+BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQwwCgYDVQQH
+DANNVFYxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGbmVhcmJ5MQswCQYDVQQD
+DAJ3czEiMCAGCSqGSIb3DQEJARYTd2VpY2VzdW5AZ29vZ2xlLmNvbTAgFw0yMTEy
+MDgwMTMxMzFaGA80NzU5MTEwNDAxMzEzMVowgYMxCzAJBgNVBAYTAlVTMRMwEQYD
+VQQIDApDYWxpZm9ybmlhMQwwCgYDVQQHDANNVFYxDzANBgNVBAoMBkdvb2dsZTEP
+MA0GA1UECwwGbmVhcmJ5MQswCQYDVQQDDAJ3czEiMCAGCSqGSIb3DQEJARYTd2Vp
+Y2VzdW5AZ29vZ2xlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AO0JW1YZ5bKHZG5B9eputz3kGREmXcWZ97dg/ODDs3+op4ulBmgaYeo5yeCy29GI
+Sjgxo4G+9fNZ7Fejrk5/LLWovAoRvVxnkRxCkTfp15jZpKNnZjT2iTRLXzNz2O04
+cC0jB81mu5vJ9a8pt+EQkuSwjDMiUi6q4Sf6IRxtTCd5a1yn9eHf1y2BbCmU+Eys
+bs97HJl9PgMCp7hP+dYDxEtNTAESg5IpJ1i7uINgPNl8d0tvJ9rOEdy0IcdeGwt/
+t0L9fIoRCePttH+idKIyDjcNyp9WtX2/wZKlsGap83rGzLdL2PI4DYJ2Ytmy8W3a
+9qFJNrhl3Q3BYgPlcCg9qQOIKq6ZJgFFH3snVDKvtSFd8b9ofK7UzD5g2SllTqDA
+4YvrdK4GETQunSjG7AC/2PpvN/FdhHm7pBi0fkgwykMh35gv0h8mmb6pBISYgr85
++GMBilNiNJ4G6j3cdOa72pvfDW5qn5dn5ks8cIgW2X1uF/GT8rR6Mb2rwhjY9eXk
+TaP0RykyzheMY/7dWeA/PdN3uMCEJEt72ZakDIswgQVPCIw8KQPIf6pl0d5hcLSV
+QzhqBaXudseVg0QlZ86iaobpZvCrW0KqQmMU5GVhEtDc2sPe5e+TCmUC/H+vo8F8
+1UYu3MJaBcpePFlgIsLhW0niUTfCq2FiNrPykOJT7U9NAgMBAAGjUzBRMB0GA1Ud
+DgQWBBQKSepRcKTv9hr8mmKjYCL7NeG2izAfBgNVHSMEGDAWgBQKSepRcKTv9hr8
+mmKjYCL7NeG2izAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQC/
+BoItafzvjYPzENY16BIkgRqJVU7IosWxGLczzg19NFu6HPa54alqkawp7RI1ZNVH
+bJjQma5ap0L+Y06/peIU9rvEtfbCkkYJwvIaSRlTlzrNwNEcj3yJMmGTr/wfIzq8
+PN1t0hihnqI8ZguOPC+sV6ARoC+ygkwaLU1oPbVvOGz9WplvSokE1mvtqKAyuDoL
+LZfWwbhxRAgwgCIEz6cPfEcgg3Xzc+L4OzmNhTTc7GNOAtvvW7Zqc2Lohb8nQMNw
+uY65yiHPNmjmc+xLHZk3jQg82tKv792JJRkVXPsIfQV087IzxFFjjvKy82rVfeaN
+F9g2EpUvdjtm8zx7K5tiDv9Es/Up7oOnoB5baLgnMAEVMTZY+4k/6BfVM5CVUu+H
+AO1yh2yeNWbzY8B+zxRef3C2Ax68lJHFyz8J1pfrGpWxML3rDmWiVDMtEk73t3g+
+lcyLYo7OW+iBn6BODRcINO4R640oyMjFz2wPSPAsU0Zj/MbgC6iaS+goS3QnyPQS
+O3hKWfwqQuA7BZ0la1n+plKH5PKxQESAbd37arzCsgQuktl33ONiwYOt6eUyHl/S
+E3ZdldkmGm9z0mcBYG9NczDBSYmtuZOGjEzIRqI5GFD2WixE+dqTzVP/kyBd4BLc
+OTmBynN/8D/qdUZNrT+tgs+mH/I2SsKYW9Zymwf7Qw==
+-----END CERTIFICATE-----
diff --git a/nearby/halfsheet/apk-certs/key.pem b/nearby/halfsheet/apk-certs/key.pem
new file mode 100644
index 0000000..e9f4288
--- /dev/null
+++ b/nearby/halfsheet/apk-certs/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDtCVtWGeWyh2Ru
+QfXqbrc95BkRJl3Fmfe3YPzgw7N/qKeLpQZoGmHqOcngstvRiEo4MaOBvvXzWexX
+o65Ofyy1qLwKEb1cZ5EcQpE36deY2aSjZ2Y09ok0S18zc9jtOHAtIwfNZrubyfWv
+KbfhEJLksIwzIlIuquEn+iEcbUwneWtcp/Xh39ctgWwplPhMrG7PexyZfT4DAqe4
+T/nWA8RLTUwBEoOSKSdYu7iDYDzZfHdLbyfazhHctCHHXhsLf7dC/XyKEQnj7bR/
+onSiMg43DcqfVrV9v8GSpbBmqfN6xsy3S9jyOA2CdmLZsvFt2vahSTa4Zd0NwWID
+5XAoPakDiCqumSYBRR97J1Qyr7UhXfG/aHyu1Mw+YNkpZU6gwOGL63SuBhE0Lp0o
+xuwAv9j6bzfxXYR5u6QYtH5IMMpDId+YL9IfJpm+qQSEmIK/OfhjAYpTYjSeBuo9
+3HTmu9qb3w1uap+XZ+ZLPHCIFtl9bhfxk/K0ejG9q8IY2PXl5E2j9EcpMs4XjGP+
+3VngPz3Td7jAhCRLe9mWpAyLMIEFTwiMPCkDyH+qZdHeYXC0lUM4agWl7nbHlYNE
+JWfOomqG6Wbwq1tCqkJjFORlYRLQ3NrD3uXvkwplAvx/r6PBfNVGLtzCWgXKXjxZ
+YCLC4VtJ4lE3wqthYjaz8pDiU+1PTQIDAQABAoICAQCt4R5CM+8enlka1IIbvann
+2cpVnUpOaNqhh6EZFBY5gDOfqafgd/H5yvh/P1UnCI5BWJBz3ew33nAT/fsglAPt
+ImEGFetNvJ9jFqXGWWCRPJ6cS35bPbp6RQwKB2JK6grH4ZmYoFLhPi5elwDPNcQ7
+xBKkc/nLSAiwtbjSTI7/qf8K0h752aTUOctpWWEnhZon00ywf4Ic3TbBatF/n/W/
+s20coEMp1cyKN/JrVQ5uD/LGwDyBModB2lWpFSxLrB14I9DWyxbxP28X7ckXLhbl
+ZdWMOyQZoa/S7n5PYT49g1Wq5BW54UpvuH5c6fpWtrgSqk1cyUR2EbTf3NAAhPLU
+PgPK8wbFMcMB3TpQDXl7USA7QX5wSv22OfhivPsHQ9szGM0f84mK0PhXYPWBiNUY
+Y8rrIjOijB4eFGDFnTIMTofAb07NxRThci710BYUqgBVTBG5N+avIesjwkikMjOI
+PwYukKSQSw/Tqxy5Z9l22xksGynBZFjEFs/WT5pDczPAktA4xW3CGxjkMsIYaOBs
+OCEujqc5+mHSywYvy8aN+nA+yPucJP5e5pLZ1qaU0tqyakCx8XeeOyP6Wfm3UAAV
+AYelBRcWcJxM51w4o5UnUnpBD+Uxiz1sRVlqa9bLJjP4M+wJNL+WaIn9D6WhPOvl
++naDC+p29ou2JzyKFDsOQQKCAQEA+Jalm+xAAPc+t/gCdAqEDo0NMA2/NG8m9BRc
+CVZRRaWVyGPeg5ziT/7caGwy2jpOZEjK0OOTCAqF+sJRDj6DDIw7nDrlxNyaXnCF
+gguQHFIYaHcjKGTs5l0vgL3H7pMFHN2qVynf4xrTuBXyT1GJ4vdWKAJbooa02c8W
+XI2fjwZ7Y8wSWrm1tn3oTTBR3N6o1GyPY6/TrL0mhpWwgx5eJeLl3GuUxOhXY5R9
+y48ziS97Dqdq75MxUOHickofCNcm7p+jA8Hg+SxLMR/kUFsXOxawmvsBqdL1XzU5
+LTS7xAEY9iMuBcO6yIxcxqBx96idjsPXx1lgARo1CpaZYCzgPQKCAQEA9BqKMN/Y
+o+T+ac99St8x3TYkk5lkvLVqlPw+EQhEqrm9EEBPntxWM5FEIpPVmFm7taGTgPfN
+KKaaNxX5XyK9B2v1QqN7XrX0nF4+6x7ao64fdpRUParIuBVctqzQWWthme66eHrf
+L86T/tkt3o/7p+Hd4Z9UT3FaAew1ggWr00xz5PJ/4b3f3mRmtNmgeTYskWMxOpSj
+bEenom4Row7sfLNeXNSWDGlzJ/lf6svvbVM2X5h2uFsxlt/Frq9ooTA3wwhnbd1i
+cFifDQ6cxF5mBpz/V/hnlHVfuXlknEZa9EQXHNo/aC9y+bR+ai05FJyK/WgqleW8
+5PBmoTReWA2MUQKCAQAnnnLkh+GnhcBEN83ESszDOO3KI9a+d5yguAH3Jv+q9voJ
+Rwl2tnFHSJo+NkhgiXxm9UcFxc9wL6Us0v1yJLpkLJFvk9984Z/kv1A36rncGaV0
+ONCspnEvQdjJTvXnax0cfaOhYrYhDuyBYVYOGDO+rabYl4+dNpTqRdwNgjDU7baK
+sEKYnRJ99FEqxDG33vDPckHkJGi7FiZmusK4EwX0SdZSq/6450LORyNJZxhSm/Oj
+4UDkz/PDLU0W5ANQOGInE+A6QBMoA0w0lx2fRPVN4I7jFHAubcXXl7b2InpugbJF
+wFOcbZZ+UgiTS4z+aKw7zbC9P9xSMKgVeO0W6/ANAoIBABe0LA8q7YKczgfAWk5W
+9iShCVQ75QheJYdqJyzIPMLHXpChbhnjE4vWY2NoL6mnrQ6qLgSsC4QTCY6n15th
+aDG8Tgi2j1hXGvXEQR/b0ydp1SxSowuJ9gvKJ0Kl7WWBg+zKvdjNNbcSvFRXCpk+
+KhXXXRB3xFwiibb+FQQXQOQ33FkzIy/snDygS0jsiSS8Gf/UPgeOP4BYRPME9Tl8
+TYKeeF9TVW7HHqOXF7VZMFrRZcpKp9ynHl2kRTH9Xo+oewG5YzHL+a8nK+q8rIR1
+Fjs2K6WDPauw6ia8nwR94H8vzX7Dwrx/Pw74c/4jfhN+UBDjeJ8tu/YPUif9SdwL
+FMECggEALdCGKfQ4vPmqI6UdfVB5hdCPoM6tUsI2yrXFvlHjSGVanC/IG9x2mpRb
+4odamLYx4G4NjP1IJSY08LFT9VhLZtRM1W3fGeboW12LTEVNrI3lRBU84rAQ1ced
+l6/DvTKJjhfwTxb/W7sqmZY5hF3QuNxs67Z8x0pe4b58musa0qFCs4Sa8qTNZKRW
+fIbxIKuvu1HSNOKkZLu6Gq8km+XIlVAaSVA03Tt+EK74MFL6+pcd7/VkS00MAYUC
+gS4ic+QFzCl5P8zl/GoX8iUFsRZQCSJkZ75VwO13pEupVwCAW8WWJO83U4jBsnJs
+ayrX7pbsnW6jsNYBUlck+RYVYkVkxA==
+-----END PRIVATE KEY-----
diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml
new file mode 100644
index 0000000..098dccb
--- /dev/null
+++ b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:interpolator="@android:interpolator/decelerate_quint">
+    <translate android:fromYDelta="100%"
+               android:toYDelta="0"
+               android:duration="900"/>
+</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml
new file mode 100644
index 0000000..1cf7401
--- /dev/null
+++ b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:interpolator="@android:interpolator/decelerate_quint">
+    <translate android:fromYDelta="0"
+               android:toYDelta="100%"
+               android:duration="500"/>
+</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml
new file mode 100644
index 0000000..9a51ddb
--- /dev/null
+++ b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:targetApi="23"
+    android:duration="@integer/half_sheet_slide_in_duration"
+    android:interpolator="@android:interpolator/fast_out_slow_in">
+  <translate
+      android:fromYDelta="100%p"
+      android:toYDelta="0%p"/>
+
+  <alpha
+      android:fromAlpha="0.0"
+      android:toAlpha="1.0"/>
+</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml
new file mode 100644
index 0000000..c589482
--- /dev/null
+++ b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:duration="@integer/half_sheet_fade_out_duration"
+    android:interpolator="@android:interpolator/fast_out_slow_in">
+
+  <translate
+      android:fromYDelta="0%p"
+      android:toYDelta="100%p"/>
+
+  <alpha
+      android:fromAlpha="1.0"
+      android:toAlpha="0.0"/>
+
+</set>
diff --git a/nearby/halfsheet/res/drawable/fastpair_outline.xml b/nearby/halfsheet/res/drawable/fastpair_outline.xml
new file mode 100644
index 0000000..6765e11
--- /dev/null
+++ b/nearby/halfsheet/res/drawable/fastpair_outline.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+  <stroke
+      android:width="1dp"
+      android:color="@color/fast_pair_notification_image_outline"/>
+</shape>
diff --git a/nearby/halfsheet/res/drawable/half_sheet_bg.xml b/nearby/halfsheet/res/drawable/half_sheet_bg.xml
new file mode 100644
index 0000000..7e7d8dd
--- /dev/null
+++ b/nearby/halfsheet/res/drawable/half_sheet_bg.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:targetApi="23">
+  <solid android:color="@color/fast_pair_half_sheet_background" />
+  <corners
+      android:topLeftRadius="16dp"
+      android:topRightRadius="16dp"
+      android:padding="8dp"/>
+</shape>
diff --git a/nearby/halfsheet/res/layout/activity_half_sheet.xml b/nearby/halfsheet/res/layout/activity_half_sheet.xml
new file mode 100644
index 0000000..4915d4b
--- /dev/null
+++ b/nearby/halfsheet/res/layout/activity_half_sheet.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="abc half sheet" />
+    </LinearLayout>
+
+</FrameLayout>
+
diff --git a/nearby/halfsheet/res/layout/fast_pair_app_launch_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_app_launch_fragment.xml
new file mode 100644
index 0000000..ad321b2
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_app_launch_fragment.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.LinearLayoutCompat
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+  <android.support.constraint.ConstraintLayout
+      android:id="@+id/image_view"
+      android:layout_width="match_parent"
+      android:layout_height="340dp"
+      android:paddingStart="12dp"
+      android:paddingEnd="12dp"
+      android:paddingTop="12dp">
+    <TextView
+        android:id="@+id/header_subtitle"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:fontFamily="google-sans"
+        android:textSize="14sp"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <ImageView
+        android:id="@+id/pairing_pic"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <Button
+        android:id="@+id/connect_btn"
+        android:text="@string/fast_pair_app_launch_button"
+        android:layout_height="@dimen/fast_pair_connect_button_height"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:background="@color/fast_pair_half_sheet_button_color"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        style="@style/HalfSheetButton" />
+
+  </android.support.constraint.ConstraintLayout>
+
+</android.support.v7.widget.LinearLayoutCompat>
diff --git a/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
new file mode 100644
index 0000000..aba9a32
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.LinearLayoutCompat
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+  <android.support.constraint.ConstraintLayout
+      android:id="@+id/image_view"
+      android:layout_width="match_parent"
+      android:layout_height="340dp"
+      android:paddingStart="12dp"
+      android:paddingEnd="12dp"
+      android:paddingTop="12dp">
+    <TextView
+        android:id="@+id/header_subtitle"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:fontFamily="google-sans"
+        android:textSize="14sp"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <ImageView
+        android:id="@+id/pairing_pic"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <ProgressBar
+        android:id="@+id/connect_progressbar"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:layout_height="2dp"
+        android:indeterminate="true"
+        android:indeterminateTint="@color/fast_pair_progress_color"
+        android:indeterminateTintMode="src_in"
+        style="?android:attr/progressBarStyleHorizontal"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"/>
+
+    <Button
+        android:id="@+id/connect_btn"
+        android:text="@string/fast_pair_app_launch_button"
+        android:layout_height="@dimen/fast_pair_connect_button_height"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:background="@color/fast_pair_half_sheet_button_color"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        style="@style/HalfSheetButton" />
+
+    <Button
+        android:id="@+id/result_action_btn"
+        android:text="@string/common_done"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        style="@style/HalfSheetButtonBorderless" />
+
+  </android.support.constraint.ConstraintLayout>
+
+</android.support.v7.widget.LinearLayoutCompat>
diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
new file mode 100644
index 0000000..24fcd83
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    tools:ignore="RtlCompat"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+  <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/image_view"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:minHeight="340dp"
+      android:paddingStart="12dp"
+      android:paddingEnd="12dp"
+      android:paddingTop="12dp"
+      android:paddingBottom="12dp">
+    <TextView
+        android:id="@+id/header_subtitle"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:fontFamily="google-sans"
+        android:textSize="14sp"
+        android:maxLines="3"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <ImageView
+        android:id="@+id/pairing_pic"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:importantForAccessibility="no"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <TextView
+        android:id="@+id/pin_code"
+        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
+        android:paddingTop="18dp"
+        android:paddingBottom="18dp"
+        android:visibility="invisible"
+        android:textSize="50sp"
+        android:letterSpacing="0.2"
+        android:fontFamily="google-sans-medium"
+        android:gravity="center"
+        android:importantForAccessibility="yes"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
+
+    <ProgressBar
+        android:id="@+id/connect_progressbar"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        android:layout_height="2dp"
+        android:indeterminate="true"
+        android:indeterminateTint="@color/fast_pair_progress_color"
+        android:indeterminateTintMode="src_in"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_marginBottom="6dp"
+        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+      <ImageView
+          android:id="@+id/info_icon"
+          android:layout_centerInParent="true"
+          android:contentDescription="@null"
+          android:layout_marginEnd="10dp"
+          android:layout_toStartOf="@id/connect_btn"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"/>
+      <Button
+          android:id="@+id/connect_btn"
+          android:text="@string/common_connect"
+          android:layout_height="wrap_content"
+          android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+          android:layout_centerInParent="true"
+          android:background="@color/fast_pair_half_sheet_button_color"
+          style="@style/HalfSheetButton" />
+    </RelativeLayout>
+
+    <Button
+        android:id="@+id/cancel_btn"
+        android:text="@string/common_done"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:gravity="start|center_vertical"
+        android:layout_marginTop="6dp"
+        android:layout_marginBottom="16dp"
+        style="@style/HalfSheetButtonBorderless"/>
+
+    <Button
+        android:id="@+id/setup_btn"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginTop="6dp"
+        android:layout_marginBottom="16dp"
+        android:background="@color/fast_pair_half_sheet_button_color"
+        android:visibility="gone"
+        android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
+        android:layout_width="wrap_content"
+        style="@style/HalfSheetButton" />
+  </androidx.constraintlayout.widget.ConstraintLayout>
+
+</LinearLayout>
diff --git a/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml
new file mode 100644
index 0000000..705aa1b
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="RtlCompat, UselessParent, MergeRootFrame"
+    android:id="@+id/background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+  <LinearLayout
+      android:id="@+id/card"
+      android:orientation="vertical"
+      android:transitionName="card"
+      android:layout_height="wrap_content"
+      android:layout_width="match_parent"
+      android:layout_gravity= "center|bottom"
+      android:paddingLeft="12dp"
+      android:paddingRight="12dp"
+      android:background="@drawable/half_sheet_bg"
+      android:accessibilityLiveRegion="polite"
+      android:gravity="bottom">
+
+    <RelativeLayout
+        android:id="@+id/toolbar_wrapper"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="20dp"
+        android:paddingRight="20dp">
+
+      <ImageView
+          android:layout_marginTop="9dp"
+          android:layout_marginBottom="9dp"
+          android:id="@+id/toolbar_image"
+          android:layout_width="42dp"
+          android:layout_height="42dp"
+          android:contentDescription="@null"
+          android:layout_toStartOf="@id/toolbar_title"
+          android:layout_centerHorizontal="true"
+          android:visibility="invisible"/>
+
+      <TextView
+          android:layout_marginTop="18dp"
+          android:layout_marginBottom="18dp"
+          android:layout_centerHorizontal="true"
+          android:id="@+id/toolbar_title"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:fontFamily="google-sans-medium"
+          android:textSize="24sp"
+          android:textColor="@color/fast_pair_half_sheet_text_color"
+          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
+    </RelativeLayout>
+
+    <FrameLayout
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+  </LinearLayout>
+
+</FrameLayout>
+
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml
new file mode 100644
index 0000000..11b8343
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:baselineAligned="false"
+    android:background="@color/fast_pair_notification_background"
+    tools:ignore="ContentDescription,UnusedAttribute,RtlCompat,Overdraw">
+
+  <LinearLayout
+      android:orientation="vertical"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:layout_weight="1"
+      android:layout_marginTop="@dimen/fast_pair_notification_padding"
+      android:layout_marginStart="@dimen/fast_pair_notification_padding"
+      android:layout_marginEnd="@dimen/fast_pair_notification_padding">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+      <TextView
+          android:id="@android:id/title"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:fontFamily="sans-serif-medium"
+          android:textSize="@dimen/fast_pair_notification_text_size"
+          android:textColor="@color/fast_pair_primary_text"
+          android:layout_marginBottom="2dp"
+          android:lines="1"/>
+
+      <TextView
+          android:id="@android:id/text2"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:textSize="@dimen/fast_pair_notification_text_size_small"
+          android:textColor="@color/fast_pair_primary_text"
+          android:layout_marginBottom="2dp"
+          android:layout_marginStart="4dp"
+          android:lines="1"/>
+    </LinearLayout>
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/fast_pair_notification_text_size"
+        android:textColor="@color/fast_pair_primary_text"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:breakStrategy="simple" />
+
+    <FrameLayout
+        android:id="@android:id/secondaryProgress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="32dp"
+        android:orientation="horizontal"
+        android:visibility="gone">
+
+      <ProgressBar
+          android:id="@android:id/progress"
+          style="?android:attr/progressBarStyleHorizontal"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:layout_gravity="center_vertical"
+          android:indeterminateTint="@color/discovery_activity_accent"/>
+
+    </FrameLayout>
+
+  </LinearLayout>
+
+  <FrameLayout
+      android:id="@android:id/icon1"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml
new file mode 100644
index 0000000..dd28947
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml
@@ -0,0 +1,7 @@
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@android:id/icon"
+    android:layout_width="@dimen/fast_pair_notification_large_image_size"
+    android:layout_height="@dimen/fast_pair_notification_large_image_size"
+    android:scaleType="fitStart"
+    tools:ignore="ContentDescription"/>
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml
new file mode 100644
index 0000000..ee1d89f
--- /dev/null
+++ b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml
@@ -0,0 +1,11 @@
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@android:id/icon"
+    android:layout_width="@dimen/fast_pair_notification_small_image_size"
+    android:layout_height="@dimen/fast_pair_notification_small_image_size"
+    android:layout_marginTop="@dimen/fast_pair_notification_padding"
+    android:layout_marginBottom="@dimen/fast_pair_notification_padding"
+    android:layout_marginStart="@dimen/fast_pair_notification_padding"
+    android:layout_marginEnd="@dimen/fast_pair_notification_padding"
+    android:scaleType="fitStart"
+    tools:ignore="ContentDescription,RtlCompat"/>
diff --git a/nearby/halfsheet/res/values/colors.xml b/nearby/halfsheet/res/values/colors.xml
new file mode 100644
index 0000000..2a2ed41
--- /dev/null
+++ b/nearby/halfsheet/res/values/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources
+    xmlns:android="http://schemas.android.com/apk/res/android">
+  <!-- Use original background color -->
+  <color name="fast_pair_notification_background">#00000000</color>
+  <color name="fast_pair_half_sheet_button_color">@android:color/system_accent1_100</color>
+  <color name="fast_pair_half_sheet_button_text">@android:color/system_neutral1_900</color>
+  <color name="fast_pair_half_sheet_button_accent_text">@android:color/system_neutral1_900</color>
+  <color name="fast_pair_progress_color">@android:color/system_accent1_600</color>
+  <color name="fast_pair_half_sheet_subtitle_color">@android:color/system_neutral2_700</color>
+  <color name="fast_pair_half_sheet_text_color">@android:color/system_neutral1_900</color>
+
+  <!-- Nearby Discoverer -->
+  <color name="discovery_activity_accent">#4285F4</color>
+
+  <!-- Fast Pair -->
+  <color name="fast_pair_primary_text">#DE000000</color>
+  <color name="fast_pair_notification_image_outline">#24000000</color>
+  <color name="fast_pair_battery_level_low">#D93025</color>
+  <color name="fast_pair_battery_level_normal">#80868B</color>
+  <color name="fast_pair_half_sheet_background">#FFFFFF</color>
+  <color name="fast_pair_half_sheet_color_accent">#1A73E8</color>
+  <color name="fast_pair_fail_progress_color">#F44336</color>
+  <color name="fast_pair_progress_back_ground">#24000000</color>
+</resources>
diff --git a/nearby/halfsheet/res/values/dimens.xml b/nearby/halfsheet/res/values/dimens.xml
new file mode 100644
index 0000000..f843042
--- /dev/null
+++ b/nearby/halfsheet/res/values/dimens.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <!-- Fast Pair notification values -->
+  <dimen name="fast_pair_halfsheet_mid_image_size">160dp</dimen>
+  <dimen name="fast_pair_notification_text_size">14sp</dimen>
+  <dimen name="fast_pair_notification_text_size_small">11sp</dimen>
+  <dimen name="fast_pair_battery_notification_empty_view_height">4dp</dimen>
+  <dimen name="fast_pair_battery_notification_margin_top">8dp</dimen>
+  <dimen name="fast_pair_battery_notification_margin_bottom">8dp</dimen>
+  <dimen name="fast_pair_battery_notification_content_height">40dp</dimen>
+  <dimen name="fast_pair_battery_notification_content_height_v2">64dp</dimen>
+  <dimen name="fast_pair_battery_notification_image_size">32dp</dimen>
+  <dimen name="fast_pair_battery_notification_image_padding">3dp</dimen>
+  <dimen name="fast_pair_half_sheet_min_height">350dp</dimen>
+  <dimen name="fast_pair_half_sheet_image_size">215dp</dimen>
+  <dimen name="fast_pair_half_sheet_land_image_size">136dp</dimen>
+  <dimen name="fast_pair_connect_button_height">36dp</dimen>
+  <dimen name="accessibility_required_min_touch_target_size">48dp</dimen>
+  <dimen name="fast_pair_half_sheet_battery_case_image_size">152dp</dimen>
+  <dimen name="fast_pair_half_sheet_battery_bud_image_size">100dp</dimen>
+  <integer name="half_sheet_battery_case_width_dp">156</integer>
+  <integer name="half_sheet_battery_case_height_dp">182</integer>
+
+  <!-- Maximum height for SliceView, override on slices/view/src/main/res/values/dimens.xml -->
+  <dimen name="abc_slice_large_height">360dp</dimen>
+
+  <dimen name="action_dialog_content_margin_left">16dp</dimen>
+  <dimen name="action_dialog_content_margin_top">70dp</dimen>
+  <dimen name="action_button_focused_elevation">4dp</dimen>
+  <!-- Subsequent Notification -->
+  <dimen name="fast_pair_notification_padding">4dp</dimen>
+  <dimen name="fast_pair_notification_large_image_size">32dp</dimen>
+  <dimen name="fast_pair_notification_small_image_size">32dp</dimen>
+  <!-- Battery Notification -->
+  <dimen name="fast_pair_battery_notification_main_view_padding">0dp</dimen>
+  <dimen name="fast_pair_battery_notification_title_image_margin_start">0dp</dimen>
+  <dimen name="fast_pair_battery_notification_title_text_margin_start">0dp</dimen>
+  <dimen name="fast_pair_battery_notification_title_text_margin_start_v2">0dp</dimen>
+  <dimen name="fast_pair_battery_notification_image_margin_start">0dp</dimen>
+
+  <dimen name="fast_pair_half_sheet_bottom_button_height">48dp</dimen>
+</resources>
diff --git a/nearby/halfsheet/res/values/ints.xml b/nearby/halfsheet/res/values/ints.xml
new file mode 100644
index 0000000..07bf9d2
--- /dev/null
+++ b/nearby/halfsheet/res/values/ints.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <integer name="half_sheet_slide_in_duration">250</integer>
+  <integer name="half_sheet_fade_out_duration">250</integer>
+</resources>
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
new file mode 100644
index 0000000..a3a2b00
--- /dev/null
+++ b/nearby/halfsheet/res/values/strings.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <string name="app_name">Nearby HalfSheet Dialog</string>
+    <string name="common_done" description="After pairing process finish button text to dismiss halfsheet">Done</string>
+    <string name="common_save">Save</string>
+    <string name="common_connect" description="Button text to start connecting process">Connect</string>
+    <string name="fast_pair_app_launch_button" description="String on app launch half sheet button.">Set up</string>
+
+</resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/values/styles.xml b/nearby/halfsheet/res/values/styles.xml
new file mode 100644
index 0000000..b48da70
--- /dev/null
+++ b/nearby/halfsheet/res/values/styles.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+  <style name="HalfSheetStyle" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+    <item name="android:windowFrame">@null</item>
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowEnterAnimation">@anim/fast_pair_half_sheet_slide_in</item>
+    <item name="android:windowExitAnimation">@anim/fast_pair_half_sheet_slide_out</item>
+    <item name="android:windowIsTranslucent">true</item>
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:windowNoTitle">true</item>
+    <item name="android:backgroundDimEnabled">true</item>
+    <item name="android:statusBarColor">@android:color/transparent</item>
+    <item name="android:fitsSystemWindows">true</item>
+    <item name="android:windowTranslucentNavigation">true</item>
+  </style>
+
+  <style name="HalfSheetButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
+    <item name="android:textColor">@color/fast_pair_half_sheet_button_accent_text</item>
+    <item name="android:backgroundTint">@color/fast_pair_half_sheet_button_color</item>
+    <item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
+    <item name="android:fontFamily">google-sans-medium</item>
+    <item name="android:textAlignment">center</item>
+    <item name="android:textAllCaps">false</item>
+  </style>
+
+  <style name="HalfSheetButtonBorderless"
+      parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
+    <item name="android:textColor">@color/fast_pair_half_sheet_button_text</item>
+    <item name="android:strokeColor">@color/fast_pair_half_sheet_button_color</item>
+    <item name="android:textAllCaps">false</item>
+    <item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
+    <item name="android:fontFamily">google-sans-medium</item>
+    <item name="android:layout_width">wrap_content</item>
+    <item name="android:layout_height">wrap_content</item>
+    <item name="android:textAlignment">center</item>
+    <item name="android:minHeight">@dimen/accessibility_required_min_touch_target_size</item>
+  </style>
+
+</resources>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
new file mode 100644
index 0000000..c495c35
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -0,0 +1,310 @@
+/*
+ * 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.nearby.halfsheet;
+
+import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
+import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
+import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
+import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET;
+import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
+import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.nearby.halfsheet.fragment.DevicePairingFragment;
+import com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment;
+import com.android.nearby.halfsheet.utils.BroadcastUtils;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.Locale;
+
+import service.proto.Cache;
+
+/**
+ * Half sheet activity to show pairing ux.
+ */
+public class HalfSheetActivity extends FragmentActivity {
+
+    public static final String EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK =
+            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK";
+    public static final String EXTRA_HALF_SHEET_PACKAGE_NAME =
+            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PACKAGE_NAME";
+    public static final String EXTRA_HALF_SHEET_CONTENT =
+            "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
+    public static final String EXTRA_TITLE =
+            "com.android.nearby.halfsheet.HALF_SHEET_TITLE";
+    public static final String EXTRA_DESCRIPTION =
+            "com.android.nearby.halfsheet.HALF_SHEET_DESCRIPTION";
+    public static final String EXTRA_HALF_SHEET_ID =
+            "com.android.nearby.halfsheet.HALF_SHEET_ID";
+    public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE =
+            "com.android.nearby.halfsheet.HALF_SHEET_IS_RETROACTIVE";
+    public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR =
+            "com.android.nearby.halfsheet.HALF_SHEET_IS_SUBSEQUENT_PAIR";
+    public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE =
+            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
+    public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
+    public static final String ACTION_HALF_SHEET_BAN_ALL_ITEM =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_BAN_ALL_ITEM";
+    public static final String ACTION_HALF_SHEET_APP_LAUNCH_CLICKED =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_APP_LAUNCH_CLICKED";
+    public static final String ACTION_HALF_SHEET_WEAR_OS_CLICKED =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_WEAR_OS_CLICKED";
+    public static final String ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION";
+    public static final String ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE =
+            "com.android.nearby.halfsheet.ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE";
+    // Intent extra contains another intent that will trigger DiscoveryChimeraService to upload
+    // device
+    // information to the cloud.
+    public static final String EXTRA_HALF_SHEET_CLOUD_SYNC_INTENT =
+            "com.android.nearby.halfsheet.HALF_SHEET_CLOUD_SYNC_INTENT";
+    // Intent extra contains the user gmail name eg. testaccount@gmail.com.
+    public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
+            "com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
+    public static final String EXTRA_HALF_SHEET_FOREGROUND =
+            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
+    public static final String EXTRA_USER_CONSENT_SYNC_CONTACTS =
+            "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_CONTACTS";
+    public static final String EXTRA_USER_CONSENT_SYNC_SMS =
+            "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_SMS";
+    public static final String EXTRA_USER_CONFIRM_PASSKEY =
+            "com.android.nearby.halfsheet.EXTRA_USER_CONFIRM_PASSKEY";
+    public static final String CLASS_NAME =
+            "com.android.nearby.halfsheet.HalfSheetActivity";
+    public static final String ACTION_HALF_SHEET_STATUS_CHANGE =
+            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_STATUS_CHANGE";
+    public static final String FINISHED_STATE = "FINISHED_STATE";
+    public static final String EXTRA_CLASSIC_MAC_ADDRESS = "EXTRA_CLASSIC_MAC_ADDRESS";
+    public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
+    @Nullable
+    private HalfSheetModuleFragment mHalfSheetModuleFragment;
+    @Nullable
+    private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        byte[] infoArray = getIntent().getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
+        String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
+        if (infoArray == null || fragmentType == null) {
+            Log.d(
+                    "HalfSheetActivity",
+                    "exit flag off or do not have enough half sheet information.");
+            finish();
+            return;
+        }
+
+        switch (fragmentType) {
+            case DEVICE_PAIRING_FRAGMENT_TYPE:
+                mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(),
+                        savedInstanceState);
+                if (mHalfSheetModuleFragment == null) {
+                    Log.d("HalfSheetActivity", "device pairing fragment has error.");
+                    finish();
+                    return;
+                }
+                break;
+            case APP_LAUNCH_FRAGMENT_TYPE:
+                // currentFragment = AppLaunchFragment.newInstance(getIntent());
+                if (mHalfSheetModuleFragment == null) {
+                    Log.v("HalfSheetActivity", "app launch fragment has error.");
+                    finish();
+                    return;
+                }
+                break;
+            default:
+                Log.w("HalfSheetActivity", "there is no valid type for half sheet");
+                finish();
+                return;
+        }
+        if (mHalfSheetModuleFragment != null) {
+            getSupportFragmentManager()
+                    .beginTransaction()
+                    .replace(R.id.fragment_container, mHalfSheetModuleFragment)
+                    .commit();
+        }
+        setContentView(R.layout.fast_pair_half_sheet);
+
+        // If the user taps on the background, then close the activity.
+        // Unless they tap on the card itself, then ignore the tap.
+        findViewById(R.id.background).setOnClickListener(v -> onCancelClicked());
+        findViewById(R.id.card)
+                .setOnClickListener(
+                        v -> Log.v("HalfSheetActivity", "card view is clicked noop"));
+        try {
+            mScanFastPairStoreItem =
+                    Cache.ScanFastPairStoreItem.parseFrom(infoArray);
+        } catch (InvalidProtocolBufferException e) {
+            Log.w(
+                    "HalfSheetActivity", "error happens when pass info to half sheet");
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
+                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, true));
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        super.onSaveInstanceState(savedInstanceState);
+        if (mHalfSheetModuleFragment != null) {
+            mHalfSheetModuleFragment.onSaveInstanceState(savedInstanceState);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        sendHalfSheetCancelBroadcast();
+    }
+
+    @Override
+    protected void onUserLeaveHint() {
+        super.onUserLeaveHint();
+        sendHalfSheetCancelBroadcast();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
+        if (fragmentType == null) {
+            return;
+        }
+        if (fragmentType.equals(DEVICE_PAIRING_FRAGMENT_TYPE)
+                && intent.getExtras() != null
+                && intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO) != null) {
+            try {
+                Cache.ScanFastPairStoreItem testScanFastPairStoreItem =
+                        Cache.ScanFastPairStoreItem.parseFrom(
+                                intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO));
+                if (mScanFastPairStoreItem != null
+                        && !testScanFastPairStoreItem.getAddress().equals(
+                        mScanFastPairStoreItem.getAddress())
+                        && testScanFastPairStoreItem.getModelId().equals(
+                        mScanFastPairStoreItem.getModelId())) {
+                    Log.d("HalfSheetActivity", "possible factory reset happens");
+                    halfSheetStateChange();
+                }
+            } catch (InvalidProtocolBufferException | NullPointerException e) {
+                Log.w("HalfSheetActivity", "error happens when pass info to half sheet");
+            }
+        }
+    }
+
+    /** This function should be called when user click empty area and cancel button. */
+    public void onCancelClicked() {
+        sendHalfSheetCancelBroadcast();
+        finish();
+    }
+
+    /** Changes the half sheet foreground state to false. */
+    public void halfSheetStateChange() {
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
+                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
+        finish();
+    }
+
+    /**
+     * Change the half sheet ban state to active sometimes users leave half sheet to go to fast pair
+     * info page we do not want the behavior to be counted as dismiss.
+     */
+    public void sendBanStateResetBroadcast() {
+        if (mScanFastPairStoreItem != null) {
+            BroadcastUtils.sendBroadcast(
+                    this,
+                    new Intent(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET)
+                            .putExtra(EXTRA_MODEL_ID,
+                                    mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT)));
+        }
+    }
+
+    private void sendHalfSheetCancelBroadcast() {
+        BroadcastUtils.sendBroadcast(
+                this,
+                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
+                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
+        if (mScanFastPairStoreItem != null) {
+            BroadcastUtils.sendBroadcast(
+                    this,
+                    new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL)
+                            .putExtra(EXTRA_MODEL_ID,
+                                    mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))
+                            .putExtra(EXTRA_HALF_SHEET_TYPE,
+                                    getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE))
+                            .putExtra(
+                                    EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
+                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
+                                            false))
+                            .putExtra(
+                                    EXTRA_HALF_SHEET_IS_RETROACTIVE,
+                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
+                                            false))
+                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()));
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting
+    public HalfSheetModuleFragment getFragmentModel() {
+        return mHalfSheetModuleFragment;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        super.setTitle(title);
+        TextView toolbarTitle = findViewById(R.id.toolbar_title);
+        toolbarTitle.setText(title);
+    }
+
+
+    /**
+     * This method converts dp unit to equivalent pixels, depending on device density.
+     *
+     * @param dp      A value in dp (density independent pixels) unit, which we need to convert into
+     *                pixels
+     * @param context Context to get resources and device specific display metrics
+     * @return A float value to represent px equivalent to dp depending on device density
+     */
+    private float convertDpToPixel(float dp, Context context) {
+        return dp
+                * ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
new file mode 100644
index 0000000..c99c130
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -0,0 +1,432 @@
+/*
+ * 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.nearby.halfsheet.fragment;
+
+import static android.text.TextUtils.isEmpty;
+
+import static com.android.nearby.halfsheet.HalfSheetActivity.ACTION_HALF_SHEET_STATUS_CHANGE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_CLASSIC_MAC_ADDRESS;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_RETROACTIVE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_PAIRING_RESURFACE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.FINISHED_STATE;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.RESULT_FAILURE;
+import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
+import static com.android.server.nearby.fastpair.Constant.DISMISS;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.server.nearby.fastpair.Constant.FAIL_STATE;
+import static com.android.server.nearby.fastpair.Constant.SUCCESS_STATE;
+import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
+import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_PRIVATE_BLE_ADDRESS;
+
+import android.animation.AnimatorSet;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.nearby.IFastPairHalfSheetCallback;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.nearby.halfsheet.HalfSheetActivity;
+import com.android.nearby.halfsheet.R;
+import com.android.nearby.halfsheet.utils.BroadcastUtils;
+import com.android.nearby.halfsheet.utils.FastPairUtils;
+import com.android.server.nearby.fastpair.UserActionHandler;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import service.proto.Cache.ScanFastPairStoreItem;
+
+/**
+ * Modularize half sheet for fast pair this fragment will show when half sheet does device pairing.
+ *
+ * <p>This fragment will handle initial pairing subsequent pairing and retroactive pairing.
+ */
+@SuppressWarnings("nullness")
+public class DevicePairingFragment extends HalfSheetModuleFragment {
+    private Button mConnectButton;
+    private Button mSetupButton;
+    private Button mCancelButton;
+    private ImageView mInfoIconButton;
+    private ProgressBar mConnectProgressBar;
+    private View mRootView;
+    private TextView mSubTitle;
+    private TextView mTitle;
+    private ImageView mImage;
+    private ScanFastPairStoreItem mScanFastPairStoreItem;
+    // This open companion app intent will be triggered after user finish Fast Pair.
+    private Intent mOpenCompanionAppIntent;
+    // Indicates that the setup button is clicked before.
+    private boolean mSetupButtonClicked = false;
+    private boolean mIsSubsequentPair = false;
+    private boolean mIsPairingResurface = false;
+    private String mBluetoothMacAddress = "";
+    private HalfSheetFragmentState mFragmentState = NOT_STARTED;
+    private AnimatorSet mAnimatorSet = new AnimatorSet();
+    // True means pairing was successful and false means failed.
+    private Boolean mPairingResult = false;
+    private Bundle mBundle;
+
+    public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
+    public static final String FAST_PAIR_CONSENT_FRAGMENT_TYPE = "FAST_PAIR_CONSENT";
+    private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
+    public static final String RESULT_FAIL = "RESULT_FAIL";
+    private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
+
+
+    /**
+     * Create certain fragment according to the intent.
+     */
+    @Nullable
+    public static HalfSheetModuleFragment newInstance(
+            Intent intent, @Nullable Bundle saveInstanceStates) {
+        Bundle args = new Bundle();
+        byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
+        boolean isRetroactive = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE, false);
+        boolean isSubsequentPair = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
+                false);
+        boolean isPairingResurface = intent.getBooleanExtra(EXTRA_HALF_SHEET_PAIRING_RESURFACE,
+                false);
+        Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
+        String title = intent.getStringExtra(EXTRA_TITLE);
+        String description = intent.getStringExtra(EXTRA_DESCRIPTION);
+        String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME);
+        String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT);
+        String publicAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
+        int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0);
+
+        args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray);
+        args.putBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE, isRetroactive);
+        args.putBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR, isSubsequentPair);
+        args.putBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE, isPairingResurface);
+        args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName);
+        args.putString(EXTRA_TITLE, title);
+        args.putString(EXTRA_DESCRIPTION, description);
+        args.putInt(EXTRA_HALF_SHEET_ID, halfSheetId);
+        args.putString(EXTRA_HALF_SHEET_CONTENT, result == null ? "" : result);
+        args.putBundle(EXTRA_BUNDLE, bundle);
+        if (saveInstanceStates != null) {
+            if (saveInstanceStates.containsKey(ARG_FRAGMENT_STATE)) {
+                args.putSerializable(
+                        ARG_FRAGMENT_STATE, saveInstanceStates.getSerializable(ARG_FRAGMENT_STATE));
+            }
+            if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_DEVICE)) {
+                args.putParcelable(
+                        BluetoothDevice.EXTRA_DEVICE,
+                        saveInstanceStates.getParcelable(BluetoothDevice.EXTRA_DEVICE));
+            }
+            if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_PAIRING_KEY)) {
+                args.putInt(
+                        BluetoothDevice.EXTRA_PAIRING_KEY,
+                        saveInstanceStates.getInt(BluetoothDevice.EXTRA_PAIRING_KEY));
+            }
+            if (saveInstanceStates.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
+                args.putBoolean(
+                        ARG_SETUP_BUTTON_CLICKED,
+                        saveInstanceStates.getBoolean(ARG_SETUP_BUTTON_CLICKED));
+            }
+            if (saveInstanceStates.containsKey(ARG_PAIRING_RESULT)) {
+                args.putBoolean(ARG_PAIRING_RESULT,
+                        saveInstanceStates.getBoolean(ARG_PAIRING_RESULT));
+            }
+        }
+        DevicePairingFragment fragment = new DevicePairingFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        mRootView =
+                inflater.inflate(
+                        R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
+                        false);
+        if (getContext() == null) {
+            Log.d("DevicePairingFragment", "can't find the attached activity");
+            return mRootView;
+        }
+        Bundle args = getArguments();
+        byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
+        boolean isRetroactive = args.getBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE);
+        mIsSubsequentPair = args.getBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR);
+        mIsPairingResurface = args.getBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE);
+        String accountName = args.getString(EXTRA_HALF_SHEET_ACCOUNT_NAME);
+        mBundle = args.getBundle(EXTRA_BUNDLE);
+        if (args.containsKey(ARG_FRAGMENT_STATE)) {
+            mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
+        }
+        if (args.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
+            mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED);
+        }
+        if (args.containsKey(ARG_PAIRING_RESULT)) {
+            mPairingResult = args.getBoolean(ARG_PAIRING_RESULT);
+        } else {
+            mPairingResult = false;
+        }
+
+        // title = ((FragmentActivity) getContext()).findViewById(R.id.toolbar_title);
+        mConnectButton = mRootView.findViewById(R.id.connect_btn);
+        mImage = mRootView.findViewById(R.id.pairing_pic);
+
+        mConnectProgressBar = mRootView.findViewById(R.id.connect_progressbar);
+        mConnectProgressBar.setVisibility(View.INVISIBLE);
+
+        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
+        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            mRootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
+            mRootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
+            mImage.getLayoutParams().height = displayMetrics.heightPixels / 2;
+            mImage.getLayoutParams().width = displayMetrics.heightPixels / 2;
+            mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2;
+            mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2;
+        }
+
+        mCancelButton = mRootView.findViewById(R.id.cancel_btn);
+        mSetupButton = mRootView.findViewById(R.id.setup_btn);
+        mInfoIconButton = mRootView.findViewById(R.id.info_icon);
+        mSubTitle = mRootView.findViewById(R.id.header_subtitle);
+        mSetupButton.setVisibility(View.GONE);
+        mInfoIconButton.setVisibility(View.GONE);
+
+        try {
+            if (storeFastPairItemBytesArray != null) {
+                mScanFastPairStoreItem =
+                        ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray);
+            }
+
+            // If the fragmentState is not NOT_STARTED, it is because the fragment was just
+            // resumed from
+            // configuration change (e.g. rotating the screen or half-sheet resurface). Let's
+            // recover the
+            // UI directly.
+            if (mFragmentState != NOT_STARTED) {
+                switch (mFragmentState) {
+                    case PAIRING:
+                        Log.d("DevicePairingFragment", "redraw for PAIRING state.");
+                        return mRootView;
+                    case RESULT_SUCCESS:
+                        Log.d("DevicePairingFragment", "redraw for RESULT_SUCCESS state.");
+                        return mRootView;
+                    case RESULT_FAILURE:
+                        Log.d("DevicePairingFragment", "redraw for RESULT_FAILURE state.");
+                        return mRootView;
+                    default:
+                        // fall-out
+                        Log.d("DevicePairingFragment",
+                                "DevicePairingFragment: not supported state");
+                }
+            }
+            if (mIsPairingResurface) {
+                // Since the Settings contextual card has sent the pairing intent, we don't send the
+                // pairing intent here.
+                onConnectClick(/* sendPairingIntent= */ false);
+            } else {
+                mSubTitle.setText(this.getArguments().getString(EXTRA_DESCRIPTION));
+                mSubTitle.setText("");
+                mConnectButton.setOnClickListener(
+                        v -> onConnectClick(/* sendPairingIntent= */ true));
+                // Pairing fail half sheet resurface
+                if (this.getArguments().getString(EXTRA_HALF_SHEET_CONTENT).equals(RESULT_FAIL)) {
+                    mFragmentState = RESULT_FAILURE;
+                    showFailInfo();
+                } else {
+                    mConnectButton.setOnClickListener(
+                            v -> onConnectClick(/* sendPairingIntent= */ true));
+                }
+            }
+        } catch (InvalidProtocolBufferException e) {
+            Log.w("DevicePairingFragment",
+                    "DevicePairingFragment: error happens when pass info to half sheet");
+        }
+        return mRootView;
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Get access to the activity's menu
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (getContext() != null) {
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(ACTION_HALF_SHEET_STATUS_CHANGE);
+            BroadcastUtils.registerReceiver(getContext(), mHalfSheetChangeReceiver, intentFilter);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (getContext() != null) {
+            BroadcastUtils.unregisterReceiver(getContext(), mHalfSheetChangeReceiver);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        super.onSaveInstanceState(savedInstanceState);
+
+        savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState);
+        savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked);
+        savedInstanceState.putBoolean(ARG_PAIRING_RESULT, mPairingResult);
+
+
+    }
+
+    @Nullable
+    private Intent createCompletionIntent(@Nullable String companionApp, @Nullable String address) {
+        if (isEmpty(companionApp)) {
+            return null;
+        } else if (FastPairUtils.isAppInstalled(companionApp, getContext())
+                && isLaunchable(companionApp)) {
+            mOpenCompanionAppIntent = createCompanionAppIntent(companionApp, address);
+            return mOpenCompanionAppIntent;
+        } else {
+            return null;
+        }
+    }
+
+    @Nullable
+    private Intent createCompanionAppIntent(String packageName, @Nullable String address) {
+        return createCompanionAppIntent(getContext(), packageName, address);
+    }
+
+    @Nullable
+    private static Intent createCompanionAppIntent(
+            Context context, String packageName, @Nullable String address) {
+        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+        BluetoothManager manager = context.getSystemService(BluetoothManager.class);
+        if (address != null && manager != null) {
+            BluetoothAdapter adapter = manager.getAdapter();
+            if (intent != null && adapter != null) {
+                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
+            }
+        }
+        return intent;
+    }
+
+    private void onConnectClick(boolean sendPairingIntent) {
+        if (mScanFastPairStoreItem == null) {
+            Log.w("DevicePairingFragment", "No pairing related information in half sheet");
+            return;
+        }
+
+        Log.d("FastPairHalfSheet", "on connect click");
+        // Allow user to setup device before connection setup.
+        // showPairingLastPhase();
+        ((Activity) getContext())
+                .findViewById(R.id.background)
+                .setOnClickListener(
+                        v ->
+                                Log.d("DevicePairingFragment",
+                                        "DevicePairingFragment: tap empty area do not dismiss "
+                                                + "half sheet when pairing."));
+        if (sendPairingIntent) {
+            try {
+                Log.d("FastPairHalfSheet", "on connect click");
+                Intent intent =
+                        new Intent(ACTION_FAST_PAIR)
+                                // Using the DiscoveryChimeraService notification id for
+                                // backwards compat
+                                .putExtra(
+                                        UserActionHandler.EXTRA_DISCOVERY_ITEM,
+                                        FastPairUtils.convertFrom(
+                                                mScanFastPairStoreItem).toByteArray())
+                                .putExtra(EXTRA_MODEL_ID, mScanFastPairStoreItem.getModelId())
+                                .putExtra(EXTRA_PRIVATE_BLE_ADDRESS,
+                                        mScanFastPairStoreItem.getAddress());
+                IFastPairHalfSheetCallback.Stub.asInterface(mBundle.getBinder(EXTRA_BINDER))
+                        .onHalfSheetConnectionConfirm(intent);
+            } catch (RemoteException e) {
+                Log.d("FastPairHalfSheet", "invoke callback fall");
+            }
+        }
+    }
+
+    private void onFailConnectClick() {
+        startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
+    }
+
+    private final BroadcastReceiver mHalfSheetChangeReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (!ACTION_HALF_SHEET_STATUS_CHANGE.equals(intent.getAction())) {
+                        return;
+                    }
+                    if (SUCCESS_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
+                        mBluetoothMacAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
+                        showSuccessInfo();
+                        if (mOpenCompanionAppIntent != null) {
+                            //((HalfSheetActivity) getContext()).halfSheetStateChange();
+                            String companionApp =
+                                    FastPairUtils.getCompanionAppFromActionUrl(
+                                            mScanFastPairStoreItem.getActionUrl());
+                            // Redirect user to companion app if user choose to setup the app.
+                            // Recreate the intent
+                            // since the correct mac address just populated.
+                            startActivity(
+                                    createCompletionIntent(companionApp, mBluetoothMacAddress));
+                        }
+                    } else if (FAIL_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
+                        showFailInfo();
+                    } else if (DISMISS.equals(intent.getStringExtra(FINISHED_STATE))) {
+                        if (getContext() != null) {
+                            HalfSheetActivity activity = (HalfSheetActivity) getContext();
+                            activity.finish();
+                        }
+                    }
+                }
+            };
+
+    private boolean isLaunchable(String companionApp) {
+        return createCompanionAppIntent(companionApp, null) != null;
+    }
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
new file mode 100644
index 0000000..88caf95
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
@@ -0,0 +1,72 @@
+/*
+ * 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.nearby.halfsheet.fragment;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+
+/** Base class for all of the half sheet fragment. */
+// TODO(b/177675274): Resolve nullness suppression.
+@SuppressWarnings("nullness")
+public abstract class HalfSheetModuleFragment extends Fragment {
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    /** UI states of the half-sheet fragment. */
+    public enum HalfSheetFragmentState {
+        NOT_STARTED,
+        SYNC_CONTACTS,
+        SYNC_SMS,
+        PROGRESSING,
+        CONFIRM_PASSKEY,
+        WRONG_PASSKEY,
+        PAIRING,
+        ADDITIONAL_SETUP_PROGRESS,
+        ADDITIONAL_SETUP_FINAL,
+        RESULT_SUCCESS,
+        RESULT_FAILURE,
+        FINISHED
+    }
+
+    /** Only used in {@link DevicePairingFragment} show pairing success info in half sheet. */
+    public void showSuccessInfo() {
+    }
+
+    /** Only used in {@link DevicePairingFragment} show pairing fail info in half sheet. */
+    public void showFailInfo() {
+    }
+
+    /**
+     * Returns the {@link HalfSheetFragmentState} to the parent activity.
+     *
+     * <p>Overrides this method if the fragment's state needs to be preserved in the parent
+     * activity.
+     */
+    public HalfSheetFragmentState getFragmentState() {
+        return HalfSheetFragmentState.NOT_STARTED;
+    }
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
new file mode 100644
index 0000000..cae2d8e
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.nearby.halfsheet.utils;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+/**
+ * Broadcast util class
+ */
+public class BroadcastUtils {
+    /**
+     * Registers a list of broadcast receiver.
+     */
+    public static void registerReceiver(
+            Context context, BroadcastReceiver receiver, IntentFilter intentFilter) {
+        context.registerReceiver(receiver, intentFilter);
+    }
+
+    /**
+     * Unregisters the already registered receiver.
+     */
+    public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
+        context.unregisterReceiver(receiver);
+    }
+
+    /**
+     * Helps send broadcast.
+     */
+    public static void sendBroadcast(Context context, Intent intent) {
+        context.sendBroadcast(intent);
+    }
+
+    private BroadcastUtils() {
+    }
+}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
new file mode 100644
index 0000000..00cd11d
--- /dev/null
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -0,0 +1,114 @@
+/*
+ * 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.nearby.halfsheet.utils;
+
+import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
+import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.net.URISyntaxException;
+
+import service.proto.Cache;
+
+/**
+ * Util class in half sheet apk
+ */
+public class FastPairUtils {
+
+    /** FastPair util method check certain app is install on the device or not. */
+    public static boolean isAppInstalled(String packageName, Context context) {
+        try {
+            context.getPackageManager().getPackageInfo(packageName, 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /** FastPair util method to properly format the action url extra. */
+    @Nullable
+    public static String getCompanionAppFromActionUrl(String actionUrl) {
+        try {
+            Intent intent = Intent.parseUri(actionUrl, Intent.URI_INTENT_SCHEME);
+            if (!intent.getAction().equals(ACTION_FAST_PAIR)) {
+                Log.e("FastPairUtils", "Companion app launch attempted from malformed action url");
+                return null;
+            }
+            return intent.getStringExtra(EXTRA_COMPANION_APP);
+        } catch (URISyntaxException e) {
+            Log.e("FastPairUtils", "FastPair: fail to get companion app info from discovery item");
+            return null;
+        }
+    }
+
+    /**
+     * Converts {@link service.proto.Cache.StoredDiscoveryItem} from
+     * {@link service.proto.Cache.ScanFastPairStoreItem}
+     */
+    public static Cache.StoredDiscoveryItem convertFrom(Cache.ScanFastPairStoreItem item) {
+        return convertFrom(item, /* isSubsequentPair= */ false);
+    }
+
+    /**
+     * Converts a {@link ScanFastPairStoreItem} to a {@link StoredDiscoveryItem}.
+     *
+     * <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the
+     * legacy Fast Pair code.
+     */
+    public static Cache.StoredDiscoveryItem convertFrom(
+            Cache.ScanFastPairStoreItem item, boolean isSubsequentPair) {
+        return Cache.StoredDiscoveryItem.newBuilder()
+                .setId(item.getModelId())
+                .setFirstObservationTimestampMillis(item.getFirstObservationTimestampMillis())
+                .setLastObservationTimestampMillis(item.getLastObservationTimestampMillis())
+                .setType(Cache.NearbyType.NEARBY_DEVICE)
+                .setActionUrl(item.getActionUrl())
+                .setActionUrlType(Cache.ResolvedUrlType.APP)
+                .setTitle(
+                        isSubsequentPair
+                                ? item.getFastPairStrings().getTapToPairWithoutAccount()
+                                : item.getDeviceName())
+                .setMacAddress(item.getAddress())
+                .setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED)
+                .setTriggerId(item.getModelId())
+                .setIconPng(item.getIconPng())
+                .setIconFifeUrl(item.getIconFifeUrl())
+                .setDescription(
+                        isSubsequentPair
+                                ? item.getDeviceName()
+                                : item.getFastPairStrings().getTapToPairWithoutAccount())
+                .setAuthenticationPublicKeySecp256R1(item.getAntiSpoofingPublicKey())
+                .setCompanionDetail(item.getCompanionDetail())
+                .setFastPairStrings(item.getFastPairStrings())
+                .setFastPairInformation(
+                        Cache.FastPairInformation.newBuilder()
+                                .setDataOnlyConnection(item.getDataOnlyConnection())
+                                .setTrueWirelessImages(item.getTrueWirelessImages())
+                                .setAssistantSupported(item.getAssistantSupported())
+                                .setCompanyName(item.getCompanyName()))
+                .build();
+    }
+
+    private FastPairUtils() {
+
+    }
+}
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index bc3f91d..b80d677 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -23,6 +23,23 @@
     ],
 }
 
+filegroup {
+    name: "nearby-service-string-res",
+    srcs: [
+        "java/**/Constant.java",
+        "java/**/UserActionHandlerBase.java",
+        "java/**/UserActionHandler.java",
+        "java/**/FastPairConstants.java",
+    ],
+}
+
+java_library {
+    name: "nearby-service-string",
+    srcs: [":nearby-service-string-res"],
+    sdk_version: "module_current",
+}
+
+
 // Main lib for nearby services.
 java_library {
     name: "service-nearby",
@@ -41,6 +58,7 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.core_core",
+        "androidx.localbroadcastmanager_localbroadcastmanager",
         "guava",
         "libprotobuf-java-lite",
         "fast-pair-lite-protos",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 464d469..12eea8b 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -28,21 +28,20 @@
  * Service implementing nearby functionality. The actual implementation is delegated to
  * {@link NearbyServiceImpl}.
  */
-// TODO(189954300): Implement nearby service.
 public class NearbyService extends SystemService {
 
-    private static final String TAG = "NearbyService";
+    public static final String TAG = "NearbyService";
     private static final boolean DBG = true;
-    private final NearbyServiceImpl mImpl;
-    private final Context mContext;
-    private final FastPairManager mFastPairManager;
 
+    private final Context mContext;
+    private final NearbyServiceImpl mImpl;
+    private final FastPairManager mFastPairManager;
     private LocatorContextWrapper mLocatorContextWrapper;
 
     public NearbyService(Context contextBase) {
         super(contextBase);
-        mImpl = new NearbyServiceImpl(contextBase);
         mContext = contextBase;
+        mImpl = new NearbyServiceImpl(contextBase);
         mLocatorContextWrapper = new LocatorContextWrapper(contextBase, null);
         mFastPairManager = new FastPairManager(mLocatorContextWrapper);
     }
@@ -52,6 +51,7 @@
         if (DBG) {
             Log.d(TAG, "Publishing NearbyService");
         }
+        publishBinderService(Context.NEARBY_SERVICE, mImpl);
         mFastPairManager.initiate();
     }
 
@@ -59,6 +59,9 @@
     public void onBootPhase(int phase) {
         if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             onSystemThirdPartyAppsCanStart();
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            // the nearby service must be functioning after this boot phase.
+            mImpl.onSystemReady();
         }
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java b/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
index c84169e..59ecae1 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -16,24 +16,92 @@
 
 package com.android.server.nearby;
 
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.Nullable;
+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.nearby.INearbyManager;
 import android.nearby.IScanListener;
 import android.nearby.ScanRequest;
+import android.util.Log;
+
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.provider.DiscoveryProviderManager;
 
 /**
- * Implementation of {@link NearbyService}.
+ * Implementation of {@link com.android.server.nearby.NearbyService}.
  */
 public class NearbyServiceImpl extends INearbyManager.Stub {
 
+    private final Context mContext;
+    private final SystemInjector mSystemInjector;
+    private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int state = intent
+                    .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            if (state == BluetoothAdapter.STATE_ON) {
+                if (mSystemInjector != null) {
+                    // Have to do this logic in listener. Even during PHASE_BOOT_COMPLETED
+                    // phase, BluetoothAdapter is not null, the BleScanner is null.
+                    Log.v(TAG, "Initiating BluetoothAdapter when Bluetooth is turned on.");
+                    mSystemInjector.onBluetoothReady();
+                }
+            }
+        }
+    };
+    private DiscoveryProviderManager mProviderManager;
+
     public NearbyServiceImpl(Context context) {
+        mContext = context;
+        mSystemInjector = new SystemInjector(context);
+        mProviderManager = new DiscoveryProviderManager(context, mSystemInjector);
     }
 
     @Override
     public void registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+        mProviderManager.registerScanListener(scanRequest, listener);
     }
 
     @Override
     public void unregisterScanListener(IScanListener listener) {
+        mProviderManager.unregisterScanListener(listener);
+    }
+
+    void onSystemReady() {
+        mContext.registerReceiver(mBluetoothReceiver,
+                new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+    }
+
+    private static final class SystemInjector implements Injector {
+        private final Context mContext;
+        @Nullable
+        private BluetoothAdapter mBluetoothAdapter;
+
+        SystemInjector(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        @Nullable
+        public BluetoothAdapter getBluetoothAdapter() {
+            return mBluetoothAdapter;
+        }
+
+        synchronized void onBluetoothReady() {
+            if (mBluetoothAdapter != null) {
+                return;
+            }
+            BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
+            if (manager == null) {
+                return;
+            }
+            mBluetoothAdapter = manager.getAdapter();
+        }
     }
 }
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
index c50e219..67d87e3 100644
--- 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
@@ -23,6 +23,7 @@
 
     public static final String EXTRA_ITEM_ID = PREFIX + "EXTRA_ITEM_ID";
     public static final String EXTRA_COMPANION_APP = ACTION_PREFIX + "EXTRA_COMPANION_APP";
+    public static final String EXTRA_MAC_ADDRESS = PREFIX + "EXTRA_MAC_ADDRESS";
 
 }
 
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
new file mode 100644
index 0000000..1477d95
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * String constant for half sheet.
+ */
+public class Constant {
+    public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
+    public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
+    public static final String SUCCESS_STATE = "SUCCESS";
+    public static final String FAIL_STATE = "FAIL";
+    public static final String DISMISS = "DISMISS";
+    public static final String NEED_CONFIRM_PASSKEY = "NEED CONFIRM PASSKEY";
+    // device support assistant additional setup
+    public static final String NEED_ADDITIONAL_SETUP = "NEED ADDITIONAL SETUP";
+    public static final String SHOW_PAIRING_WITHOUT_INTERACTION =
+            "SHOW_PAIRING_WITHOUT_INTERACTION";
+    public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL =
+            "com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL";
+    public static final String ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET =
+            "com.android.nearby.ACTION_FAST_PAIR_BAN_STATE_RESET";
+    public static final String EXTRA_HALF_SHEET_INFO =
+            "com.android.nearby.halfsheet.HALF_SHEET";
+    public static final String EXTRA_HALF_SHEET_TYPE =
+            "com.android.nearby.halfsheet.HALF_SHEET_TYPE";
+    public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING";
+
+}
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 ff4bce2..247339e 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,17 +16,34 @@
 
 package com.android.server.nearby.fastpair;
 
+
+import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
+
 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.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.nearby.NearbyDevice;
+import android.nearby.ScanCallback;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Base64;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 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;
@@ -42,9 +59,14 @@
 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.HalfSheetCallback;
 import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
+import com.android.server.nearby.util.Environment;
+
+import com.google.protobuf.ByteString;
 
 import java.security.GeneralSecurityException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -52,7 +74,9 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
 
+import service.proto.Cache;
 import service.proto.Rpcs;
 
 /**
@@ -63,18 +87,37 @@
 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*/
+    private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
+    /** A notification ID which should be dismissed */
     public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
+    public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
+    // Temp action deleted when the scanner is ready
+    public static final String ACTION_START_PAIRING = "NEARBY_START_PAIRING";
+    public static final String EXTRA_ADDRESS = "ADDRESS";
+
     private static Executor sFastPairExecutor;
 
-    LocatorContextWrapper mLocatorContextWrapper;
-    IntentFilter mIntentFilter;
-    Locator mLocator;
+    private String mHalfSheetApkPkgName;
+
+    final LocatorContextWrapper mLocatorContextWrapper;
+    final IntentFilter mIntentFilter;
+    final Locator mLocator;
     private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
                 Log.d("FastPairService", " screen on");
+            } else if (intent.getAction().equals(ACTION_START_PAIRING)) {
+                String address = intent.getStringExtra(EXTRA_ADDRESS);
+                String testPublicKey =
+                        "E4kxROcAj2SPlqrxHgXOm_yF-XIMSS91pFrfmeLcxULWyn0nK8i52PPkoC4r"
+                                + "LKRM2kZzNgT8bhDwK5njJAjiOg";
+                showHalfSheet(Cache.ScanFastPairStoreItem.newBuilder().setAddress(address)
+                        .setAntiSpoofingPublicKey(ByteString.copyFrom(
+                                Base64.decode(testPublicKey, Base64.URL_SAFE
+                                        | Base64.NO_WRAP | Base64.NO_PADDING)))
+                        .build());
+                Log.d("FastPairService", "start pair " + address);
             } else {
                 Log.d("FastPairService", " screen off");
             }
@@ -91,12 +134,31 @@
                 Rpcs.GetObservedDeviceResponse.newBuilder().build();
     }
 
+    final ScanCallback mScanCallback = new ScanCallback() {
+        @Override
+        public void onDiscovered(@NonNull NearbyDevice device) {
+            Log.d("FastPair", "Ondiscovery " + device.getName());
+        }
+
+        @Override
+        public void onUpdated(@NonNull NearbyDevice device) {
+
+        }
+
+        @Override
+        public void onLost(@NonNull NearbyDevice device) {
+
+        }
+    };
+
     /**
      * Function called when nearby service start.
      */
     public void initiate() {
         mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
         mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mIntentFilter.addAction("NEARBY_START_PAIRING");
+
         mLocatorContextWrapper.getContext()
                 .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
 
@@ -110,7 +172,6 @@
         mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver);
     }
 
-
     /**
      * Starts fast pair process.
      */
@@ -191,7 +252,9 @@
                         item.getFastPairInformation().getDataOnlyConnection());
             }
             FastPairConnection connection = new FastPairDualConnection(
-                    context, item.getMacAddress(), prefsBuilder.build(), null);
+                    context, item.getMacAddress(),
+                    // Change to prefsBuilder.build when the api integration complete
+                    Preferences.builderFromGmsLog().build(), null);
             pairingProgressHandlerBase.onPairingSetupCompleted();
 
             FastPairConnection.SharedSecret sharedSecret;
@@ -202,7 +265,7 @@
                                         : item.getAuthenticationPublicKeySecp256R1());
 
                 byte[] key = pairingProgressHandlerBase.getKeyForLocalCache(accountKey,
-                                connection, sharedSecret);
+                        connection, sharedSecret);
 
                 // We don't cache initial pairing case here but cache it when upload to footprints.
                 if (key != null) {
@@ -251,6 +314,9 @@
         }
     }
 
+    /**
+     * This function should only be called on main thread since there is no lock
+     */
     private static Executor getExecutor() {
         if (sFastPairExecutor != null) {
             return sFastPairExecutor;
@@ -267,4 +333,70 @@
         BluetoothManager manager = context.getSystemService(BluetoothManager.class);
         return manager == null ? null : manager.getAdapter();
     }
+
+    /**
+     * Gets the package name of HalfSheet.apk
+     * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
+     * race condition check. Since there is no lock for mHalfSheetApkPkgName.
+     */
+    String getHalfSheetApkPkgName() {
+        if (mHalfSheetApkPkgName != null) {
+            return mHalfSheetApkPkgName;
+        }
+        List<ResolveInfo> resolveInfos = mLocatorContextWrapper.getContext()
+                .getPackageManager().queryIntentActivities(
+                        new Intent(ACTION_RESOURCES_APK),
+                        PackageManager.MATCH_SYSTEM_ONLY);
+
+        // remove apps that don't live in the nearby apex
+        resolveInfos.removeIf(info ->
+                !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
+
+        if (resolveInfos.isEmpty()) {
+            // Resource APK not loaded yet, print a stack trace to see where this is called from
+            Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
+                            + " APK is installed or package manager can't resolve correctly!",
+                    new IllegalStateException());
+            return null;
+        }
+
+        if (resolveInfos.size() > 1) {
+            // multiple apps found, log a warning, but continue
+            Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
+                    + resolveInfos.stream()
+                    .map(info -> info.activityInfo.applicationInfo.packageName)
+                    .collect(Collectors.joining(", ")));
+        }
+
+        // Assume the first ResolveInfo is the one we're looking for
+        ResolveInfo info = resolveInfos.get(0);
+        mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
+        Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
+        return mHalfSheetApkPkgName;
+    }
+
+    /**
+     * Invokes half sheet in the other apk. This function can only be called in Nearby because other
+     * app can't get the correct component name.
+     */
+    void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
+        if (mLocatorContextWrapper != null) {
+            String packageName = getHalfSheetApkPkgName();
+            HalfSheetCallback callback = new HalfSheetCallback();
+            callback.setmFastPairController(Locator
+                    .getFromContextWrapper(mLocatorContextWrapper, FastPairController.class));
+            Bundle bundle = new Bundle();
+            bundle.putBinder(EXTRA_BINDER, callback);
+            mLocatorContextWrapper.getContext()
+                    .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
+                                    .putExtra(EXTRA_HALF_SHEET_INFO,
+                                            scanFastPairStoreItem.toByteArray())
+                                    .putExtra(EXTRA_HALF_SHEET_TYPE, DEVICE_PAIRING_FRAGMENT_TYPE)
+                                    .putExtra(EXTRA_BUNDLE, bundle)
+                                    .setComponent(new ComponentName(packageName,
+                                            packageName + ".HalfSheetActivity")),
+                            UserHandle.CURRENT);
+
+        }
+    }
 }
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 5444e82..e92bb8a 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
@@ -18,9 +18,12 @@
 
 import android.content.Context;
 
+import com.android.server.nearby.common.eventloop.EventLoop;
 import com.android.server.nearby.common.locator.Locator;
 import com.android.server.nearby.common.locator.Module;
 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 java.time.Clock;
 import java.time.Instant;
@@ -37,10 +40,16 @@
     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(FootprintsDeviceManager.class)) {
+            locator.bind(FootprintsDeviceManager.class, new FootprintsDeviceManager());
+        } else if (type.equals(EventLoop.class)) {
+            locator.bind(EventLoop.class, EventLoop.newInstance("NearbyFastPair"));
         } 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(FastPairHalfSheetManager.class)) {
+            locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager());
         } else if (type.equals(Clock.class)) {
             locator.bind(Clock.class, new Clock() {
                 @Override
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
index f2b0d59..674633d 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
@@ -25,4 +25,7 @@
 
     public static final String EXTRA_DISCOVERY_ITEM = PREFIX + "EXTRA_DISCOVERY_ITEM";
     public static final String EXTRA_FAST_PAIR_SECRET = PREFIX + "EXTRA_FAST_PAIR_SECRET";
+    public static final String ACTION_FAST_PAIR = ACTION_PREFIX + "ACTION_FAST_PAIR";
+    public static final String EXTRA_PRIVATE_BLE_ADDRESS =
+            ACTION_PREFIX + "EXTRA_PRIVATE_BLE_ADDRESS";
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
index d59305b..4c43965 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
@@ -17,6 +17,7 @@
 package com.android.server.nearby.fastpair.halfsheet;
 
 import android.bluetooth.BluetoothDevice;
+import android.util.Log;
 
 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
 
@@ -29,7 +30,7 @@
      * Shows pairing fail half sheet.
      */
     public void showPairingFailed() {
-
+        Log.d("FastPairHalfSheetManager", "show fail half sheet");
     }
 
     /**
@@ -42,30 +43,38 @@
     /**
      * Show passkey confirmation info on half sheet
      */
-    public void showPasskeyConfirmation(BluetoothDevice device, int passkey) {}
+    public void showPasskeyConfirmation(BluetoothDevice device, int passkey) {
+    }
 
     /**
      * This function will handle pairing steps for half sheet.
      */
-    public void showPairingHalfSheet(DiscoveryItem item) {}
+    public void showPairingHalfSheet(DiscoveryItem item) {
+        Log.d("FastPairHalfSheetManager", "show pairing half sheet");
+    }
 
     /**
      * Shows pairing success info.
      */
-    public void showPairingSuccessHalfSheet(String address){}
+    public void showPairingSuccessHalfSheet(String address) {
+        Log.d("FastPairHalfSheetManager", "show success half sheet");
+    }
 
     /**
      * Removes dismiss runnable.
      */
-    public void disableDismissRunnable(){}
+    public void disableDismissRunnable() {
+    }
 
     /**
      * Destroys the bluetooth pairing controller.
      */
-    public void destroyBluetoothPairController(){}
+    public void destroyBluetoothPairController() {
+    }
 
     /**
      * Notify manager the pairing has finished.
      */
-    public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {}
+    public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
new file mode 100644
index 0000000..2c792ed
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
@@ -0,0 +1,52 @@
+/*
+ * 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.content.Intent;
+import android.nearby.IFastPairHalfSheetCallback;
+import android.util.Log;
+
+import com.android.server.nearby.fastpair.FastPairController;
+
+
+/**
+ * Callback to send ux action back to nearby service.
+ */
+public class HalfSheetCallback extends IFastPairHalfSheetCallback.Stub {
+    private FastPairController mFastPairController;
+
+    public HalfSheetCallback() {
+    }
+
+    /**
+     * Set function for Fast Pair controller.
+     */
+    public void setmFastPairController(FastPairController fastPairController) {
+        mFastPairController = fastPairController;
+    }
+
+    /**
+     * Half Sheet connection button clicked.
+     */
+    @Override
+    public void onHalfSheetConnectionConfirm(Intent intent) {
+        Log.d("FastPairHalfSheet", "Call back receiver");
+        if (mFastPairController != null) {
+            mFastPairController.pair(intent);
+        }
+    }
+}
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
index 760b4e0..ccd7e5e 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
@@ -76,7 +76,8 @@
 
 
         Log.v("PairingHandler",
-                "PairingProgressHandler:Create %s for pairing");
+                "PairingProgressHandler:Create "
+                        + item.getMacAddress() + " for pairing");
         return pairingProgressHandlerBase;
     }
 
@@ -134,7 +135,7 @@
      * <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.
+     * 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.
@@ -180,7 +181,7 @@
      */
     public void onPairingSuccess(String address) {
         Log.v("PairingHandler", "PairingProgressHandler:onPairingSuccess with address: "
-                        + maskBluetoothAddress(address));
+                + maskBluetoothAddress(address));
     }
 
     private static void optInFootprintsForInitialPairing(
diff --git a/nearby/service/java/com/android/server/nearby/injector/Injector.java b/nearby/service/java/com/android/server/nearby/injector/Injector.java
new file mode 100644
index 0000000..d221722
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/injector/Injector.java
@@ -0,0 +1,32 @@
+/*
+ * 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.injector;
+
+import android.bluetooth.BluetoothAdapter;
+
+/**
+ *  Nearby dependency injector. To be used for accessing various Nearby class instances and as a
+ *  handle for mock injection.
+ */
+public interface Injector {
+
+    /**
+     * Get the BluetoothAdapter for BleDiscoveryProvider to scan.
+     */
+    BluetoothAdapter getBluetoothAdapter();
+
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
new file mode 100644
index 0000000..7cc859c
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
@@ -0,0 +1,138 @@
+/*
+ * 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.provider;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.content.Context;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Base class for all discovery providers.
+ *
+ * @hide
+ */
+public abstract class AbstractDiscoveryProvider {
+
+    protected final Context mContext;
+    protected final DiscoveryProviderController mController;
+    protected final Executor mExecutor;
+    protected Listener mListener;
+
+    /**
+     * Interface for listening to discovery providers.
+     */
+    public interface Listener {
+        /**
+         * Called when a provider has a new nearby device available. May be invoked from any thread.
+         */
+        void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice);
+    }
+
+    protected AbstractDiscoveryProvider(Context context, Executor executor) {
+        mContext = context;
+        mExecutor = executor;
+        mController = new Controller();
+    }
+
+    /**
+     * Callback invoked when the provider is started, and signals that other callback invocations
+     * can now be expected. Always implies that the provider request is set to the empty request.
+     * Always invoked on the provider executor.
+     */
+    protected void onStart() { }
+
+    /**
+     * Callback invoked when the provider is stopped, and signals that no further callback
+     * invocations will occur (until a further call to {@link #onStart()}. Always invoked on the
+     * provider executor.
+     */
+    protected void onStop() { }
+
+    /**
+     * Callback invoked to inform the provider of a new provider request which replaces any prior
+     * provider request. Always invoked on the provider executor.
+     */
+    protected void invalidateScanMode() { }
+
+    /**
+     * Retrieves the controller for this discovery provider. Should never be invoked by subclasses,
+     * as a discovery provider should not be controlling itself. Using this method from subclasses
+     * could also result in deadlock.
+     */
+    protected DiscoveryProviderController getController() {
+        return mController;
+    }
+
+    private class Controller implements DiscoveryProviderController {
+
+        private boolean mStarted = false;
+        private @ScanRequest.ScanMode int mScanMode;
+
+        @Override
+        public void setListener(@Nullable Listener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public boolean isStarted() {
+            return mStarted;
+        }
+
+        @Override
+        public void start() {
+            if (mStarted) {
+                Log.d(TAG, "Provider already started.");
+                return;
+            }
+            mStarted = true;
+            mExecutor.execute(AbstractDiscoveryProvider.this::onStart);
+        }
+
+        @Override
+        public void stop() {
+            if (!mStarted) {
+                Log.d(TAG, "Provider already stopped.");
+                return;
+            }
+            mStarted = false;
+            mExecutor.execute(AbstractDiscoveryProvider.this::onStop);
+        }
+
+        @Override
+        public void setProviderScanMode(@ScanRequest.ScanMode int scanMode) {
+            if (mScanMode == scanMode) {
+                Log.d(TAG, "Provider already in desired scan mode.");
+                return;
+            }
+            mScanMode = scanMode;
+            mExecutor.execute(AbstractDiscoveryProvider.this::invalidateScanMode);
+        }
+
+        @ScanRequest.ScanMode
+        @Override
+        public int getProviderScanMode() {
+            return mScanMode;
+        }
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
new file mode 100644
index 0000000..ae7f133
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -0,0 +1,183 @@
+/*
+ * 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.provider;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.nearby.NearbyDevice;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.server.nearby.common.bluetooth.fastpair.Constants;
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.util.ForegroundThread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Discovery provider that uses Bluetooth Low Energy to do scanning.
+ */
+public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
+
+    private static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID);
+    // Don't block the thread as it may be used by other services.
+    private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
+    private final Injector mInjector;
+    private android.bluetooth.le.ScanCallback mScanCallback =
+            new android.bluetooth.le.ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult scanResult) {
+                    NearbyDeviceParcelable.Builder builder = new NearbyDeviceParcelable.Builder();
+                    builder.setMedium(NearbyDevice.Medium.BLE)
+                            .setRssi(scanResult.getRssi())
+                            .setBluetoothAddress(scanResult.getDevice().getAddress());
+
+                    ScanRecord record = scanResult.getScanRecord();
+                    if (record != null) {
+                        String deviceName = record.getDeviceName();
+                        if (deviceName != null) {
+                            builder.setName(record.getDeviceName());
+                        }
+                        Map<ParcelUuid, byte[]> serviceDataMap = record.getServiceData();
+                        byte[] fastPairData = serviceDataMap.get(FAST_PAIR_UUID);
+                        if (fastPairData != null) {
+                            builder.setData(serviceDataMap.get(FAST_PAIR_UUID));
+                        }
+                    }
+                    mExecutor.execute(() -> mListener.onNearbyDeviceDiscovered(builder.build()));
+                }
+
+                @Override
+                public void onScanFailed(int errorCode) {
+                    Log.w(TAG, "BLE Scan failed with error code " + errorCode);
+                }
+            };
+
+    public BleDiscoveryProvider(Context context, Injector injector) {
+        super(context, NEARBY_EXECUTOR);
+        mInjector = injector;
+    }
+
+    private static List<ScanFilter> getScanFilters() {
+        List<ScanFilter> scanFilterList = new ArrayList<>();
+        scanFilterList.add(
+                new ScanFilter.Builder()
+                        .setServiceData(FAST_PAIR_UUID, new byte[]{0}, new byte[]{0})
+                        .build());
+        return scanFilterList;
+    }
+
+    private boolean isBleAvailable() {
+        BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+        if (adapter == null) {
+            return false;
+        }
+
+        return adapter.getBluetoothLeScanner() != null;
+    }
+
+    @Nullable
+    private BluetoothLeScanner getBleScanner() {
+        BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+        if (adapter == null) {
+            return null;
+        }
+        return adapter.getBluetoothLeScanner();
+    }
+
+    @Override
+    protected void onStart() {
+        if (isBleAvailable()) {
+            Log.d(TAG, "BleDiscoveryProvider started.");
+            startScan(getScanFilters(), getScanSettings(), mScanCallback);
+            return;
+        }
+        Log.w(TAG, "Cannot start BleDiscoveryProvider because Ble is not available.");
+        mController.stop();
+    }
+
+    @Override
+    protected void onStop() {
+        BluetoothLeScanner bluetoothLeScanner = getBleScanner();
+        if (bluetoothLeScanner == null) {
+            Log.w(TAG, "BleDiscoveryProvider failed to stop BLE scanning "
+                    + "because BluetoothLeScanner is null.");
+            return;
+        }
+        bluetoothLeScanner.stopScan(mScanCallback);
+    }
+
+    @Override
+    protected void invalidateScanMode() {
+        onStop();
+        onStart();
+    }
+
+    private void startScan(
+            List<ScanFilter> scanFilters, ScanSettings scanSettings,
+            android.bluetooth.le.ScanCallback scanCallback) {
+        try {
+            BluetoothLeScanner bluetoothLeScanner = getBleScanner();
+            if (bluetoothLeScanner == null) {
+                Log.w(TAG, "BleDiscoveryProvider failed to start BLE scanning "
+                        + "because BluetoothLeScanner is null.");
+            }
+            bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);
+        } catch (NullPointerException | IllegalStateException | SecurityException e) {
+            // NullPointerException:
+            //   - Commonly, on Blackberry devices. b/73299795
+            //   - Rarely, on other devices. b/75285249
+            // IllegalStateException:
+            // Caused if we call BluetoothLeScanner.startScan() after Bluetooth has turned off.
+            // SecurityException:
+            // refer to b/177380884
+            Log.w(TAG, "BleDiscoveryProvider failed to start BLE scanning.", e);
+        }
+    }
+
+    private ScanSettings getScanSettings() {
+        int bleScanMode = ScanSettings.SCAN_MODE_LOW_POWER;
+        switch (mController.getProviderScanMode()) {
+            case ScanRequest.SCAN_MODE_LOW_LATENCY:
+                bleScanMode = ScanSettings.SCAN_MODE_LOW_LATENCY;
+                break;
+            case ScanRequest.SCAN_MODE_BALANCED:
+                bleScanMode = ScanSettings.SCAN_MODE_BALANCED;
+                break;
+            case ScanRequest.SCAN_MODE_LOW_POWER:
+                bleScanMode = ScanSettings.SCAN_MODE_LOW_POWER;
+                break;
+            case ScanRequest.SCAN_MODE_NO_POWER:
+                bleScanMode = ScanSettings.SCAN_MODE_OPPORTUNISTIC;
+                break;
+        }
+        return new ScanSettings.Builder().setScanMode(bleScanMode).build();
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java
new file mode 100644
index 0000000..469f623
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderController.java
@@ -0,0 +1,58 @@
+/*
+ * 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.provider;
+
+import android.annotation.Nullable;
+import android.nearby.ScanRequest;
+
+/**
+ * Interface for controlling discovery providers.
+ */
+interface DiscoveryProviderController {
+
+    /**
+     * Sets the listener which can expect to receive all state updates from after this point.
+     * May be invoked at any time.
+     */
+    void setListener(@Nullable AbstractDiscoveryProvider.Listener listener);
+
+    /**
+     * Returns true if in the started state.
+     */
+    boolean isStarted();
+
+    /**
+     * Starts the discovery provider. Must be invoked before any other method (except
+     * {@link #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}).
+     */
+    void start();
+
+    /**
+     * Stops the discovery provider. No other methods may be invoked after this method (except
+     * {@link #setListener(AbstractDiscoveryProvider.Listener)} (Listener)}), until {@link #start()} is called again.
+     */
+    void stop();
+
+    /**
+     * Sets the desired scan mode.
+     */
+    void setProviderScanMode(@ScanRequest.ScanMode int scanMode);
+
+    /** Gets the controller scan mode. */
+    @ScanRequest.ScanMode
+    int getProviderScanMode();
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
new file mode 100644
index 0000000..fb1afb0
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -0,0 +1,203 @@
+/*
+ * 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.provider;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.content.Context;
+import android.nearby.IScanListener;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.nearby.injector.Injector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Manages all aspects of discovery providers.
+ */
+public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener {
+
+    protected final Object mLock = new Object();
+    private final Context mContext;
+    private final BleDiscoveryProvider mBleDiscoveryProvider;
+    private @ScanRequest.ScanMode int mScanMode;
+
+    @GuardedBy("mLock")
+    private Map<IScanListener, ScanListenerRecord> mScanTypeScanListenerRecordMap;
+
+    @Override
+    public void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice) {
+        synchronized (mLock) {
+            for (IScanListener listener : mScanTypeScanListenerRecordMap.keySet()) {
+                ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listener);
+                if (record == null) {
+                    Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
+                    continue;
+                }
+                try {
+                    record.getScanListener().onDiscovered(
+                            PrivacyFilter.filter(record.getScanRequest().getScanType(),
+                                    nearbyDevice));
+                } catch (RemoteException e) {
+                    Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e);
+                }
+            }
+        }
+    }
+
+    public DiscoveryProviderManager(Context context, Injector injector) {
+        mContext = context;
+        mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector);
+        mScanTypeScanListenerRecordMap = new HashMap<>();
+    }
+
+    /**
+     * Registers the listener in the manager and starts scan according to the requested scan mode.
+     */
+    public void registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+        synchronized (mLock) {
+            if (mScanTypeScanListenerRecordMap.containsKey(listener)) {
+                ScanRequest savedScanRequest = mScanTypeScanListenerRecordMap.get(
+                        listener).getScanRequest();
+                if (scanRequest.equals(savedScanRequest)) {
+                    Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
+                    return;
+                }
+            }
+
+            startProviders(scanRequest);
+
+            mScanTypeScanListenerRecordMap.put(listener,
+                    new ScanListenerRecord(scanRequest, listener));
+            if (mScanMode < scanRequest.getScanMode()) {
+                mScanMode = scanRequest.getScanMode();
+                invalidateProviderScanMode();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the listener in the manager and adjusts the scan mode if necessary afterwards.
+     */
+    public void unregisterScanListener(IScanListener listener) {
+        synchronized (mLock) {
+            if (!mScanTypeScanListenerRecordMap.containsKey(listener)) {
+                Log.w(TAG,
+                        "Cannot unregister the scanRequest %s because the request is never "
+                                + "registered.");
+                return;
+            }
+
+            ScanListenerRecord removedRecord = mScanTypeScanListenerRecordMap.remove(listener);
+            if (mScanTypeScanListenerRecordMap.isEmpty()) {
+                stopProviders();
+                return;
+            }
+
+            // Removes current highest scan mode requested and sets the next highest scan mode.
+            if (removedRecord.getScanRequest().getScanMode() == mScanMode) {
+                @ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER;
+                // find the next highest scan mode;
+                for (ScanListenerRecord record : mScanTypeScanListenerRecordMap.values()) {
+                    @ScanRequest.ScanMode int scanMode = record.getScanRequest().getScanMode();
+                    if (scanMode > highestScanModeRequested) {
+                        highestScanModeRequested = scanMode;
+                    }
+                }
+                if (mScanMode != highestScanModeRequested) {
+                    mScanMode = highestScanModeRequested;
+                    invalidateProviderScanMode();
+                }
+            }
+        }
+    }
+
+    private void startProviders(ScanRequest scanRequest) {
+        if (scanRequest.isEnableBle()) {
+            startBleProvider(scanRequest);
+        }
+    }
+
+    private void startBleProvider(ScanRequest scanRequest) {
+        if (!mBleDiscoveryProvider.getController().isStarted()) {
+            Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
+            mBleDiscoveryProvider.getController().start();
+            mBleDiscoveryProvider.getController().setListener(this);
+            mBleDiscoveryProvider.getController().setProviderScanMode(scanRequest.getScanMode());
+        }
+    }
+
+    private void stopProviders() {
+        stopBleProvider();
+    }
+
+    private void stopBleProvider() {
+        mBleDiscoveryProvider.getController().stop();
+    }
+
+    private void invalidateProviderScanMode() {
+        if (!mBleDiscoveryProvider.getController().isStarted()) {
+            Log.d(TAG,
+                    "Skip invalidating BleDiscoveryProvider scan mode because the provider not "
+                            + "started.");
+            return;
+        }
+        mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
+    }
+
+    private static class ScanListenerRecord {
+
+        private final ScanRequest mScanRequest;
+
+        private final IScanListener mScanListener;
+
+
+        ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener) {
+            mScanListener = iScanListener;
+            mScanRequest = scanRequest;
+        }
+
+        IScanListener getScanListener() {
+            return mScanListener;
+        }
+
+        ScanRequest getScanRequest() {
+            return mScanRequest;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof ScanListenerRecord) {
+                ScanListenerRecord otherScanListenerRecord = (ScanListenerRecord) other;
+                return Objects.equals(mScanRequest, otherScanListenerRecord.mScanRequest)
+                        && Objects.equals(mScanListener, otherScanListenerRecord.mScanListener);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return  Objects.hash(mScanListener, mScanRequest);
+        }
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/PrivacyFilter.java b/nearby/service/java/com/android/server/nearby/provider/PrivacyFilter.java
new file mode 100644
index 0000000..5c37f68
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/PrivacyFilter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.provider;
+
+import android.annotation.Nullable;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+
+/**
+ * Class strips out privacy sensitive data before delivering the callbacks to client.
+ */
+public class PrivacyFilter {
+
+    /**
+     * Strips sensitive data from {@link NearbyDeviceParcelable} according to
+     * different {@link android.nearby.ScanRequest.ScanType}s.
+     */
+    @Nullable
+    public static NearbyDeviceParcelable filter(@ScanRequest.ScanType int scanType,
+            NearbyDeviceParcelable scanResult) {
+        return scanResult;
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java
new file mode 100644
index 0000000..dc131e7
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/Environment.java
@@ -0,0 +1,63 @@
+/*
+ * 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.util;
+
+import android.content.ApexEnvironment;
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
+
+import java.io.File;
+
+/**
+ * Provides function to make sure the function caller is from the same apex.
+ */
+public class Environment {
+    /**
+     * NEARBY apex name.
+     */
+    private static final String NEARBY_APEX_NAME = "com.android.nearby";
+
+    /**
+     * The path where the Nearby apex is mounted.
+     * Current value = "/apex/com.android.nearby"
+     */
+    private static final String NEARBY_APEX_PATH =
+            new File("/apex", NEARBY_APEX_NAME).getAbsolutePath();
+
+    /**
+     * Nearby shared folder.
+     */
+    public static File getNearbyDirectory() {
+        return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME).getDeviceProtectedDataDir();
+    }
+
+    /**
+     * Nearby user specific folder.
+     */
+    public static File getNearbyDirectory(int userId) {
+        return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME)
+                .getCredentialProtectedDataDirForUser(UserHandle.of(userId));
+    }
+
+    /**
+     * Returns true if the app is in the nearby apex, false otherwise.
+     * Checks if the app's path starts with "/apex/com.android.nearby".
+     */
+    public static boolean isAppInNearbyApex(ApplicationInfo appInfo) {
+        return appInfo.sourceDir.startsWith(NEARBY_APEX_PATH);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/ForegroundThread.java b/nearby/service/java/com/android/server/nearby/util/ForegroundThread.java
new file mode 100644
index 0000000..793ab9a
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/ForegroundThread.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Shared singleton foreground thread.
+ */
+public class ForegroundThread extends HandlerThread {
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static ForegroundThread sInstance;
+    @GuardedBy("sLock")
+    private static Handler sHandler;
+    @GuardedBy("sLock")
+    private static Executor sExecutor;
+
+    private ForegroundThread() {
+        super(ForegroundThread.class.getName());
+    }
+
+    @GuardedBy("sLock")
+    private static void ensureInstanceLocked() {
+        if (sInstance == null) {
+            sInstance = new ForegroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sExecutor = new HandlerExecutor(sHandler);
+        }
+    }
+
+    /**
+     * Get the singleton instance of thi class.
+     *
+     * @return the singleton instance of thi class
+     */
+    @NonNull
+    public static ForegroundThread get() {
+        synchronized (sLock) {
+            ensureInstanceLocked();
+            return sInstance;
+        }
+    }
+
+    /**
+     * Get the {@link Handler} for this thread.
+     *
+     * @return the {@link Handler} for this thread.
+     */
+    @NonNull
+    public static Handler getHandler() {
+        synchronized (sLock) {
+            ensureInstanceLocked();
+            return sHandler;
+        }
+    }
+
+    /**
+     * Get the {@link Executor} for this thread.
+     *
+     * @return the {@link Executor} for this thread.
+     */
+    @NonNull
+    public static Executor getExecutor() {
+        synchronized (sLock) {
+            ensureInstanceLocked();
+            return sExecutor;
+        }
+    }
+
+    /**
+     * An adapter {@link Executor} that posts all executed tasks onto the given
+     * {@link Handler}.
+     */
+    private static class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+
+        HandlerExecutor(@NonNull Handler handler) {
+            mHandler = Preconditions.checkNotNull(handler);
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            if (!mHandler.post(command)) {
+                throw new RejectedExecutionException(mHandler + " is shutting down");
+            }
+        }
+    }
+}
diff --git a/nearby/service/proto/src/fastpair/cache.proto b/nearby/service/proto/src/fastpair/cache.proto
index bf80b58..12731fb 100644
--- a/nearby/service/proto/src/fastpair/cache.proto
+++ b/nearby/service/proto/src/fastpair/cache.proto
@@ -304,3 +304,158 @@
 
   reserved 5;
 }
+
+// A locally cached Fast Pair device associating an account key with the
+// bluetooth address of the device.
+message StoredFastPairItem {
+  // The device's public mac address.
+  string mac_address = 1;
+
+  // The account key written to the device.
+  bytes account_key = 2;
+
+  // When user need to update provider name, enable this value to trigger
+  // writing new name to provider.
+  bool need_to_update_provider_name = 3;
+
+  // The retry times to update name into provider.
+  int32 update_name_retries = 4;
+
+  // Latest firmware version from the server.
+  string latest_firmware_version = 5;
+
+  // The firmware version that is on the device.
+  string device_firmware_version = 6;
+
+  // The timestamp from the last time we fetched the firmware version from the
+  // device.
+  int64 last_check_firmware_timestamp_millis = 7;
+
+  // The timestamp from the last time we fetched the firmware version from
+  // server.
+  int64 last_server_query_timestamp_millis = 8;
+
+  // Only allows one bloom filter check process to create gatt connection and
+  // try to read the firmware version value.
+  bool can_read_firmware = 9;
+
+  // Device's model id.
+  string model_id = 10;
+
+  // Features that this Fast Pair device supports.
+  repeated FastPairFeature features = 11;
+
+  // Keeps the stored discovery item in local cache, we can have most
+  // information of fast pair device locally without through footprints, i.e. we
+  // can have most fast pair features locally.
+  StoredDiscoveryItem discovery_item = 12;
+
+  // When true, the latest uploaded event to FMA is connected. We use
+  // it as the previous ACL state when getting the BluetoothAdapter STATE_OFF to
+  // determine if need to upload the disconnect event to FMA.
+  bool fma_state_is_connected = 13;
+
+  // Device's buffer size range.
+  repeated BufferSizeRange buffer_size_range = 18;
+
+  // The additional account key if this device could be associated with multiple
+  // accounts. Notes that for this device, the account_key field is the basic
+  // one which will not be associated with the accounts.
+  repeated bytes additional_account_key = 19;
+
+  // Deprecated fields.
+  reserved 14, 15, 16, 17;
+}
+
+// Contains information about Fast Pair devices stored through our scanner.
+// Next ID: 29
+message ScanFastPairStoreItem {
+  // Device's model id.
+  string model_id = 1;
+
+  // Device's RSSI value
+  int32 rssi = 2;
+
+  // Device's tx power
+  int32 tx_power = 3;
+
+  // Bytes of item icon in PNG format displayed in Discovery item list.
+  bytes icon_png = 4;
+
+  // A FIFE URL of the item icon displayed in Discovery item list.
+  string icon_fife_url = 28;
+
+  // Device name like "Bose QC 35".
+  string device_name = 5;
+
+  // Client timestamp when user last saw Fast Pair device.
+  int64 last_observation_timestamp_millis = 6;
+
+  // Action url after user click the notification.
+  string action_url = 7;
+
+  // Device's bluetooth address.
+  string address = 8;
+
+  // The computed threshold rssi value that would trigger FastPair notifications
+  int32 threshold_rssi = 9;
+
+  // Populated with the contents of the bloom filter in the event that
+  // the scanned device is advertising a bloom filter instead of a model id
+  bytes bloom_filter = 10;
+
+  // Device name from the BLE scan record
+  string ble_device_name = 11;
+
+  // Strings used for the FastPair UI
+  FastPairStrings fast_pair_strings = 12;
+
+  // A key used to authenticate advertising device.
+  // See NearbyItem.authentication_public_key_secp256r1 for more information.
+  bytes anti_spoofing_public_key = 13;
+
+  // When true, Fast Pair will only create a bond with the device and not
+  // attempt to connect any profiles (for example, A2DP or HFP).
+  bool data_only_connection = 14;
+
+  // The type of the manufacturer (first party, third party, etc).
+  int32 manufacturer_type_num = 15;
+
+  // Additional images that are attached specifically for true wireless Fast
+  // Pair devices.
+  TrueWirelessHeadsetImages true_wireless_images = 16;
+
+  // When true, this device can support assistant function.
+  bool assistant_supported = 17;
+
+  // Optional, the name of the company producing this Fast Pair device.
+  string company_name = 18;
+
+  // Features supported by the Fast Pair device.
+  FastPairFeature features = 19;
+
+  // The interaction type that this scan should trigger
+  InteractionType interaction_type = 20;
+
+  // The copy of the advertisement bytes, used to pass along to other
+  // apps that use Fast Pair as the discovery vehicle.
+  bytes full_ble_record = 21;
+
+  // Companion app related information
+  CompanionAppDetails companion_detail = 22;
+
+  // Client timestamp when user first saw Fast Pair device.
+  int64 first_observation_timestamp_millis = 23;
+
+  // The type of the device (wearable, headphones, etc).
+  int32 device_type_num = 24;
+
+  // The type of notification (app launch smart setup, etc).
+  NotificationType notification_type = 25;
+
+  // The customized title.
+  string customized_title = 26;
+
+  // The customized description.
+  string customized_description = 27;
+}
diff --git a/nearby/service/proto/src/fastpair/rpcs.proto b/nearby/service/proto/src/fastpair/rpcs.proto
index 384d471..0399d09 100644
--- a/nearby/service/proto/src/fastpair/rpcs.proto
+++ b/nearby/service/proto/src/fastpair/rpcs.proto
@@ -313,3 +313,17 @@
   string fast_pair_tv_connect_device_no_account_description = 24;
 }
 
+// The buffer size range of a Fast Pair devices support dynamic buffer size.
+message BufferSizeRange {
+  // The max buffer size in ms.
+  int32 max_size = 1;
+
+  // The min buffer size in ms.
+  int32 min_size = 2;
+
+  // The default buffer size in ms.
+  int32 default_size = 3;
+
+  // The codec of this buffer size range.
+  int32 codec = 4;
+}
diff --git a/nearby/tests/Android.bp b/nearby/tests/unit/Android.bp
similarity index 98%
rename from nearby/tests/Android.bp
rename to nearby/tests/unit/Android.bp
index fc0a700..5287891 100644
--- a/nearby/tests/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -47,7 +47,6 @@
         "mockito-target",
         "platform-test-annotations",
         "service-nearby",
-        "truth-prebuilt",
     ],
     test_suites: [
         "general-tests",
diff --git a/nearby/tests/AndroidManifest.xml b/nearby/tests/unit/AndroidManifest.xml
similarity index 100%
rename from nearby/tests/AndroidManifest.xml
rename to nearby/tests/unit/AndroidManifest.xml
diff --git a/nearby/tests/AndroidTest.xml b/nearby/tests/unit/AndroidTest.xml
similarity index 100%
rename from nearby/tests/AndroidTest.xml
rename to nearby/tests/unit/AndroidTest.xml
diff --git a/nearby/tests/src/android/nearby/ScanRequestTest.java b/nearby/tests/unit/src/android/nearby/ScanRequestTest.java
similarity index 98%
rename from nearby/tests/src/android/nearby/ScanRequestTest.java
rename to nearby/tests/unit/src/android/nearby/ScanRequestTest.java
index fe80a15..fdb6db1 100644
--- a/nearby/tests/src/android/nearby/ScanRequestTest.java
+++ b/nearby/tests/unit/src/android/nearby/ScanRequestTest.java
@@ -29,8 +29,8 @@
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/ble/BleRecordTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
similarity index 96%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
index 5084e7e..6b3ed2e 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
@@ -22,8 +22,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
index a45bd77..0273af9 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
@@ -28,8 +28,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
similarity index 99%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
index d17f373..50f826d 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
@@ -28,8 +28,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
index 0e13133..b1f5148 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
@@ -21,8 +21,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.primitives.Bytes;
 
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
index 36ebb7e..fa0a890 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
@@ -21,8 +21,8 @@
 import android.bluetooth.BluetoothAdapter;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
index c5bf407..dbb01f2 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
@@ -20,8 +20,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
index eda899d..94eba0e 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
@@ -22,8 +22,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
index dc459f1..d3867f0 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
@@ -23,8 +23,8 @@
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
 
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
index 81651bc..072fe00 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
@@ -22,8 +22,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.hash.Hashing;
 import com.google.protobuf.ByteString;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
index b930dfa..ff69a57 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
@@ -22,8 +22,8 @@
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
index 9457e95..f5f2988 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
@@ -27,8 +27,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.base.Preconditions;
 import com.google.common.hash.Hashing;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
index 84c2386..9a7c430 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
@@ -25,8 +25,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
similarity index 98%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
index ba51408..9e3f3da 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
@@ -28,8 +28,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
similarity index 99%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
index 0146ab3..ded1fb7 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
@@ -21,8 +21,8 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming;
 import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.Timing;
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
similarity index 97%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
index 53c7615..4f206c4 100644
--- a/nearby/tests/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
@@ -23,8 +23,8 @@
 import android.bluetooth.BluetoothGatt;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.collect.ImmutableSet;
 
diff --git a/nearby/tests/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/fastpair/FastPairManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
diff --git a/nearby/tests/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
similarity index 100%
rename from nearby/tests/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
rename to nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java