Merge changes Ife1279ca,Ib1eb78c4 into tm-dev

* changes:
  Add new e2e test case: seeker_show_halfsheet_test.
  Extract FastPairTestDataProviderService out from the snippet.
diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp
index e3c8bb1..579baa5 100644
--- a/nearby/tests/multidevices/clients/Android.bp
+++ b/nearby/tests/multidevices/clients/Android.bp
@@ -18,18 +18,18 @@
 
 android_library {
     name: "NearbyMultiDevicesClientsLib",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
-    ],
+    srcs: ["src/**/*.kt"],
     sdk_version: "test_current",
     static_libs: [
         "MoblySnippetHelperLib",
         "NearbyFastPairProviderLib",
+        "NearbyFastPairSeekerSharedLib",
         "androidx.test.core",
-        "gson-prebuilt-jar",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
         "kotlin-stdlib",
         "mobly-snippet-lib",
+        "truth-prebuilt",
     ],
 }
 
diff --git a/nearby/tests/multidevices/clients/AndroidManifest.xml b/nearby/tests/multidevices/clients/AndroidManifest.xml
index 9641756..86c10b2 100644
--- a/nearby/tests/multidevices/clients/AndroidManifest.xml
+++ b/nearby/tests/multidevices/clients/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application>
         <meta-data
@@ -37,31 +38,14 @@
             android:name="mobly-snippets"
             android:value="android.nearby.multidevices.fastpair.seeker.FastPairSeekerSnippet,
                            android.nearby.multidevices.fastpair.provider.FastPairProviderSimulatorSnippet" />
-
-        <!-- Fast Pair Data Provider Service which acts as an "overlay" to the
-             framework Fast Pair Data Provider. Only supported on Android T and later.
-             All overlays are protected from non-system access via WRITE_SECURE_SETTINGS.
-             Must stay in the same process as Nearby Discovery Service.
-        -->
-        <service
-            android:name=".fastpair.seeker.dataprovider.FastPairTestDataProviderService"
-            android:exported="true"
-            android:permission="android.permission.WRITE_SECURE_SETTINGS"
-            android:visibleToInstantApps="true">
-            <intent-filter>
-                <action android:name="android.nearby.action.FAST_PAIR_DATA_PROVIDER" />
-            </intent-filter>
-
-            <meta-data
-                android:name="instantapps.clients.allowed"
-                android:value="true" />
-            <meta-data
-                android:name="serviceVersion"
-                android:value="1" />
-        </service>
     </application>
 
     <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Nearby Mainline Module Instrumentation Test"
+        android:targetPackage="android.nearby.multidevices" />
+
+    <instrumentation
         android:name="com.google.android.mobly.snippet.SnippetRunner"
         android:label="Nearby Mainline Module Mobly Snippet"
         android:targetPackage="android.nearby.multidevices" />
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
index 522774c..617eac1 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
@@ -17,11 +17,14 @@
 package android.nearby.multidevices.fastpair.seeker
 
 import android.content.Context
-import android.content.Intent
+import android.nearby.FastPairDeviceMetadata
 import android.nearby.NearbyManager
 import android.nearby.ScanCallback
 import android.nearby.ScanRequest
-import android.nearby.multidevices.fastpair.seeker.dataprovider.FastPairTestDataCache
+import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
+import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
+import android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest
+import android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest
 import androidx.test.core.app.ApplicationProvider
 import com.google.android.mobly.snippet.Snippet
 import com.google.android.mobly.snippet.rpc.AsyncRpc
@@ -32,6 +35,7 @@
 class FastPairSeekerSnippet : Snippet {
     private val appContext = ApplicationProvider.getApplicationContext<Context>()
     private val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+    private val fastPairTestDataManager = FastPairTestDataManager(appContext)
     private lateinit var scanCallback: ScanCallback
 
     /**
@@ -60,17 +64,32 @@
         nearbyManager.stopScan(scanCallback)
     }
 
-    /** Starts the Fast Pair seeker pairing. */
-    @Rpc(description = "Starts the Fast Pair seeker pairing.")
-    fun startPairing(modelId: String, address: String) {
-        Log.i("Starts the Fast Pair seeker pairing.")
+    /** Waits and asserts the HalfSheet showed for Fast Pair pairing.
+     *
+     * @param modelId the expected model id to be associated with the HalfSheet.
+     * @param timeout the number of seconds to wait before giving up.
+     */
+    @Rpc(description = "Waits the HalfSheet showed for Fast Pair pairing.")
+    fun waitAndAssertHalfSheetShowed(modelId: String, timeout: Int) {
+        Log.i("Waits and asserts the HalfSheet showed for Fast Pair model $modelId.")
 
-        val scanIntent = Intent().apply {
-            action = FAST_PAIR_MANAGER_ACTION_START_PAIRING
-            putExtra(FAST_PAIR_MANAGER_EXTRA_MODEL_ID, modelId.toByteArray())
-            putExtra(FAST_PAIR_MANAGER_EXTRA_ADDRESS, address)
-        }
-        appContext.sendBroadcast(scanIntent)
+        val deviceMetadata: FastPairDeviceMetadata =
+            fastPairTestDataManager.testDataCache.getFastPairDeviceMetadata(modelId)
+                ?: throw IllegalArgumentException(
+                    "Can't find $modelId-FastPairAntispoofKeyDeviceMetadata pair in " +
+                            "FastPairTestDataCache."
+                )
+        val deviceName = deviceMetadata.name!!
+        val initialPairingDescriptionTemplateText = deviceMetadata.initialPairingDescription!!
+
+        CheckNearbyHalfSheetUiTest(
+            waitHalfSheetPopupTimeoutSeconds = timeout,
+            halfSheetTitleText = deviceName,
+            halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
+                deviceName,
+                FAKE_TEST_ACCOUNT_NAME
+            )
+        ).checkNearbyHalfSheetUi()
     }
 
     /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
@@ -81,7 +100,7 @@
     @Rpc(description = "Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.")
     fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) {
         Log.i("Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.")
-        FastPairTestDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
+        fastPairTestDataManager.sendAntispoofKeyDeviceMetadata(modelId, json)
     }
 
     /** Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.
@@ -91,14 +110,32 @@
     @Rpc(description = "Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.")
     fun putAccountKeyDeviceMetadata(json: String) {
         Log.i("Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.")
-        FastPairTestDataCache.putAccountKeyDeviceMetadata(json)
+        fastPairTestDataManager.sendAccountKeyDeviceMetadata(json)
     }
 
     /** Dumps all FastPairAccountKeyDeviceMetadata from the test data cache. */
     @Rpc(description = "Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.")
     fun dumpAccountKeyDeviceMetadata(): String {
         Log.i("Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.")
-        return FastPairTestDataCache.dumpAccountKeyDeviceMetadata()
+        return fastPairTestDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson()
+    }
+
+    /** Writes into {@link Settings} whether Fast Pair scan is enabled.
+     *
+     * @param enable whether the Fast Pair scan should be enabled.
+     */
+    @Rpc(description = "Writes into Settings whether Fast Pair scan is enabled.")
+    fun setFastPairScanEnabled(enable: Boolean) {
+        Log.i("Writes into Settings whether Fast Pair scan is enabled.")
+        NearbyManager.setFastPairScanEnabled(appContext, enable)
+    }
+
+    /** Dismisses the half sheet UI if showed. */
+    @Rpc(description = "Dismisses the half sheet UI if showed.")
+    fun dismissHalfSheet() {
+        Log.i("Dismisses the half sheet UI if showed.")
+
+        DismissNearbyHalfSheetUiTest().dismissHalfSheet()
     }
 
     /** Invokes when the snippet runner shutting down. */
@@ -106,12 +143,6 @@
         super.shutdown()
 
         Log.i("Resets the Fast Pair test data cache.")
-        FastPairTestDataCache.reset()
-    }
-
-    companion object {
-        private const val FAST_PAIR_MANAGER_ACTION_START_PAIRING = "NEARBY_START_PAIRING"
-        private const val FAST_PAIR_MANAGER_EXTRA_MODEL_ID = "MODELID"
-        private const val FAST_PAIR_MANAGER_EXTRA_ADDRESS = "ADDRESS"
+        fastPairTestDataManager.sendResetCache()
     }
 }
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt
new file mode 100644
index 0000000..291aad8
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 android.nearby.multidevices.fastpair.seeker.data
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.nearby.fastpair.seeker.*
+import android.util.Log
+
+/** Manage local FastPairTestDataCache and send to/sync from the remote cache in data provider. */
+class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() {
+    val testDataCache = FastPairTestDataCache()
+
+    /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into local and remote cache.
+     *
+     * @param modelId a string of model id to be associated with.
+     * @param json a string of FastPairAntispoofKeyDeviceMetadata JSON object.
+     */
+    fun sendAntispoofKeyDeviceMetadata(modelId: String, json: String) {
+        Intent().also { intent ->
+            intent.action = ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
+            intent.putExtra(DATA_MODEL_ID_STRING_KEY, modelId)
+            intent.putExtra(DATA_JSON_STRING_KEY, json)
+            context.sendBroadcast(intent)
+        }
+        testDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
+    }
+
+    /** Puts account key device metadata to local and remote cache.
+     *
+     * @param json a string of FastPairAccountKeyDeviceMetadata JSON array.
+     */
+    fun sendAccountKeyDeviceMetadata(json: String) {
+        Intent().also { intent ->
+            intent.action = ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
+            intent.putExtra(DATA_JSON_STRING_KEY, json)
+            context.sendBroadcast(intent)
+        }
+        testDataCache.putAccountKeyDeviceMetadata(json)
+    }
+
+    /** Clears local and remote cache. */
+    fun sendResetCache() {
+        context.sendBroadcast(Intent(ACTION_RESET_TEST_DATA_CACHE))
+        testDataCache.reset()
+    }
+
+    /**
+     * Callback method for receiving Intent broadcast from FastPairTestDataProvider.
+     *
+     * See [BroadcastReceiver#onReceive].
+     *
+     * @param context the Context in which the receiver is running.
+     * @param intent the Intent being received.
+     */
+    override fun onReceive(context: Context, intent: Intent) {
+        when (intent.action) {
+            ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA -> {
+                Log.d(TAG, "ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA received!")
+                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
+                testDataCache.putAccountKeyDeviceMetadata(json)
+            }
+            else -> Log.d(TAG, "Unknown action received!")
+        }
+    }
+
+    companion object {
+        private const val TAG = "FastPairTestDataManager"
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..84b5e89
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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 android.nearby.multidevices.fastpair.seeker.ui
+
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** An instrumented test to check Nearby half sheet UI showed correctly.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest \
+ * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class CheckNearbyHalfSheetUiTest {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+    private val waitHalfSheetPopupTimeoutMs: Long
+    private val halfSheetTitleText: String
+    private val halfSheetSubtitleText: String
+
+    constructor() {
+        val arguments: Bundle = InstrumentationRegistry.getArguments()
+        waitHalfSheetPopupTimeoutMs = arguments.getLong(
+            WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
+            DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
+        )
+        halfSheetTitleText =
+            arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
+        halfSheetSubtitleText =
+            arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
+    }
+
+    constructor(
+        waitHalfSheetPopupTimeoutSeconds: Int,
+        halfSheetTitleText: String,
+        halfSheetSubtitleText: String
+    ) {
+        this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
+        this.halfSheetTitleText = halfSheetTitleText
+        this.halfSheetSubtitleText = halfSheetSubtitleText
+    }
+
+    @Test
+    fun checkNearbyHalfSheetUi() {
+        // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
+        val isConnectButtonShowed = device.wait(
+            Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
+            waitHalfSheetPopupTimeoutMs
+        )
+        assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
+            .that(isConnectButtonShowed).isTrue()
+
+        val halfSheetTitle =
+            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
+        assertThat(halfSheetTitle).isNotNull()
+        assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
+
+        val halfSheetSubtitle =
+            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
+        assertThat(halfSheetSubtitle).isNotNull()
+        assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
+
+        val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
+        assertThat(deviceImage).isNotNull()
+
+        val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
+        assertThat(infoButton).isNotNull()
+    }
+
+    companion object {
+        private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 1000L
+        private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
+        private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
+                "appear on devices linked with nearby-mainline-fpseeker@google.com"
+        private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
+        private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
+        private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..1d99d26
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 android.nearby.multidevices.fastpair.seeker.ui
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** An instrumented test to dismiss Nearby half sheet UI.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest \
+ * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class DismissNearbyHalfSheetUiTest {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @Test
+    fun dismissHalfSheet() {
+        device.pressHome()
+        device.waitForIdle()
+
+        assertWithMessage("Fail to dismiss Nearby half sheet.").that(
+            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton)
+        ).isNull()
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
new file mode 100644
index 0000000..c94ff01
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 android.nearby.multidevices.fastpair.seeker.ui
+
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+
+/** UiMap for Nearby Mainline Half Sheet. */
+object NearbyHalfSheetUiMap {
+    private const val PACKAGE_NAME = "com.google.android.nearby.halfsheet"
+    private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
+    private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
+    private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
+
+    object DevicePairingFragment {
+        val halfSheetTitle: BySelector =
+            By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
+        val halfSheetSubtitle: BySelector =
+            By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
+        val deviceImage: BySelector =
+            By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+        val connectButton: BySelector =
+            By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
+        val infoButton: BySelector =
+            By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
new file mode 100644
index 0000000..a6d51ca
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
@@ -0,0 +1,46 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "NearbyFastPairSeekerSharedLib",
+    srcs: ["shared/**/*.kt"],
+    sdk_version: "test_current",
+    static_libs: [
+        "guava",
+        "gson-prebuilt-jar",
+    ],
+}
+
+android_library {
+    name: "NearbyFastPairSeekerDataProviderLib",
+    srcs: ["src/**/*.kt"],
+    sdk_version: "test_current",
+    static_libs: ["NearbyFastPairSeekerSharedLib"],
+}
+
+android_app {
+    name: "NearbyFastPairSeekerDataProvider",
+    sdk_version: "test_current",
+    certificate: "platform",
+    static_libs: ["NearbyFastPairSeekerDataProviderLib"],
+    optimize: {
+        enabled: true,
+        shrink: true,
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml
new file mode 100644
index 0000000..1d62f04
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.nearby.fastpair.seeker.dataprovider">
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <application>
+        <!-- Fast Pair Data Provider Service which acts as an "overlay" to the
+             framework Fast Pair Data Provider. Only supported on Android T and later.
+             All overlays are protected from non-system access via WRITE_SECURE_SETTINGS.
+             Must stay in the same process as Nearby Discovery Service.
+        -->
+        <service
+            android:name=".FastPairTestDataProviderService"
+            android:exported="true"
+            android:permission="android.permission.WRITE_SECURE_SETTINGS"
+            android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="android.nearby.action.FAST_PAIR_DATA_PROVIDER" />
+            </intent-filter>
+
+            <meta-data
+                android:name="instantapps.clients.allowed"
+                android:value="true" />
+            <meta-data
+                android:name="serviceVersion"
+                android:value="1" />
+        </service>
+    </application>
+
+</manifest>
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags
new file mode 100644
index 0000000..15debab
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags
@@ -0,0 +1,19 @@
+# Keep all receivers/service classes.
+-keep class android.nearby.fastpair.seeker.** {
+     *;
+}
+
+# Keep names for easy debugging.
+-dontobfuscate
+
+# Necessary to allow debugging.
+-keepattributes *
+
+# By default, proguard leaves all classes in their original package, which
+# needlessly repeats com.google.android.apps.etc.
+-repackageclasses ""
+
+# Allows proguard to make private and protected methods and fields public as
+# part of optimization. This lets proguard inline trivial getter/setter
+# methods.
+-allowaccessmodification
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt
new file mode 100644
index 0000000..6070140
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 android.nearby.fastpair.seeker
+
+const val FAKE_TEST_ACCOUNT_NAME = "nearby-mainline-fpseeker@google.com"
+
+const val ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA =
+    "android.nearby.fastpair.seeker.action.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA"
+const val ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA =
+    "android.nearby.fastpair.seeker.action.ACCOUNT_KEY_DEVICE_METADATA"
+const val ACTION_RESET_TEST_DATA_CACHE = "android.nearby.fastpair.seeker.action.RESET"
+const val ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA =
+    "android.nearby.fastpair.seeker.action.WRITE_ACCOUNT_KEY_DEVICE_METADATA"
+
+const val DATA_JSON_STRING_KEY = "json"
+const val DATA_MODEL_ID_STRING_KEY = "modelId"
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
similarity index 90%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt
rename to nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
index 3e9d6d3..80ca855 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker.dataprovider
+package android.nearby.fastpair.seeker
 
 import android.nearby.FastPairAccountKeyDeviceMetadata
 import android.nearby.FastPairAntispoofKeyDeviceMetadata
@@ -25,30 +25,46 @@
 import com.google.gson.annotations.SerializedName
 
 /** Manage a cache of Fast Pair test data for testing. */
-object FastPairTestDataCache {
+class FastPairTestDataCache {
     private val gson = Gson()
-    val accountKeyDeviceMetadata = mutableListOf<FastPairAccountKeyDeviceMetadata>()
-    val antispoofKeyDeviceMetadataMap =
-        mutableMapOf<String, FastPairAntispoofKeyDeviceMetadata>()
+    private val accountKeyDeviceMetadataList = mutableListOf<FastPairAccountKeyDeviceMetadata>()
+    private val antispoofKeyDeviceMetadataDataMap =
+        mutableMapOf<String, FastPairAntispoofKeyDeviceMetadataData>()
 
     fun putAccountKeyDeviceMetadata(json: String) {
-        accountKeyDeviceMetadata +=
+        accountKeyDeviceMetadataList +=
             gson.fromJson(json, Array<FastPairAccountKeyDeviceMetadataData>::class.java)
                 .map { it.toFastPairAccountKeyDeviceMetadata() }
     }
 
-    fun dumpAccountKeyDeviceMetadata(): String =
-        gson.toJson(accountKeyDeviceMetadata.map { FastPairAccountKeyDeviceMetadataData(it) })
-
-    fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) {
-        antispoofKeyDeviceMetadataMap[modelId] =
-            gson.fromJson(json, FastPairAntispoofKeyDeviceMetadataData::class.java)
-                .toFastPairAntispoofKeyDeviceMetadata()
+    fun putAccountKeyDeviceMetadata(accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata) {
+        accountKeyDeviceMetadataList += accountKeyDeviceMetadata
     }
 
+    fun getAccountKeyDeviceMetadataList(): List<FastPairAccountKeyDeviceMetadata> =
+        accountKeyDeviceMetadataList.toList()
+
+    fun dumpAccountKeyDeviceMetadataAsJson(metadata: FastPairAccountKeyDeviceMetadata): String =
+        gson.toJson(FastPairAccountKeyDeviceMetadataData(metadata))
+
+    fun dumpAccountKeyDeviceMetadataListAsJson(): String =
+        gson.toJson(accountKeyDeviceMetadataList.map { FastPairAccountKeyDeviceMetadataData(it) })
+
+    fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) {
+        antispoofKeyDeviceMetadataDataMap[modelId] =
+            gson.fromJson(json, FastPairAntispoofKeyDeviceMetadataData::class.java)
+    }
+
+    fun getAntispoofKeyDeviceMetadata(modelId: String): FastPairAntispoofKeyDeviceMetadata? {
+        return antispoofKeyDeviceMetadataDataMap[modelId]?.toFastPairAntispoofKeyDeviceMetadata()
+    }
+
+    fun getFastPairDeviceMetadata(modelId: String): FastPairDeviceMetadata? =
+        antispoofKeyDeviceMetadataDataMap[modelId]?.deviceMeta?.toFastPairDeviceMetadata()
+
     fun reset() {
-        accountKeyDeviceMetadata.clear()
-        antispoofKeyDeviceMetadataMap.clear()
+        accountKeyDeviceMetadataList.clear()
+        antispoofKeyDeviceMetadataDataMap.clear()
     }
 
     data class FastPairAccountKeyDeviceMetadataData(
@@ -296,7 +312,8 @@
                 .build()
         }
     }
-
-    private fun String.base64Decode(): ByteArray = BaseEncoding.base64().decode(this)
-    private fun ByteArray.base64Encode(): String = BaseEncoding.base64().encode(this)
 }
+
+private fun String.base64Decode(): ByteArray = BaseEncoding.base64().decode(this)
+
+private fun ByteArray.base64Encode(): String = BaseEncoding.base64().encode(this)
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt
new file mode 100644
index 0000000..ffc02a0
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 android.nearby.fastpair.seeker.data
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.nearby.FastPairAccountKeyDeviceMetadata
+import android.nearby.fastpair.seeker.*
+import android.util.Log
+
+/** Manage local FastPairTestDataCache and receive/update the remote cache in test snippet. */
+class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() {
+    val testDataCache = FastPairTestDataCache()
+
+    /** Writes a FastPairAccountKeyDeviceMetadata into local and remote cache.
+     *
+     * @param accountKeyDeviceMetadata the FastPairAccountKeyDeviceMetadata to write.
+     */
+    fun writeAccountKeyDeviceMetadata(accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata) {
+        testDataCache.putAccountKeyDeviceMetadata(accountKeyDeviceMetadata)
+
+        val json =
+            testDataCache.dumpAccountKeyDeviceMetadataAsJson(accountKeyDeviceMetadata)
+        Intent().also { intent ->
+            intent.action = ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA
+            intent.putExtra(DATA_JSON_STRING_KEY, json)
+            context.sendBroadcast(intent)
+        }
+    }
+
+    /**
+     * Callback method for receiving Intent broadcast from test snippet.
+     *
+     * See [BroadcastReceiver#onReceive].
+     *
+     * @param context the Context in which the receiver is running.
+     * @param intent the Intent being received.
+     */
+    override fun onReceive(context: Context, intent: Intent) {
+        when (intent.action) {
+            ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -> {
+                Log.d(TAG, "ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA received!")
+                val modelId = intent.getStringExtra(DATA_MODEL_ID_STRING_KEY)!!
+                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
+                testDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
+            }
+            ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -> {
+                Log.d(TAG, "ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA received!")
+                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
+                testDataCache.putAccountKeyDeviceMetadata(json)
+            }
+            ACTION_RESET_TEST_DATA_CACHE -> {
+                Log.d(TAG, "ACTION_RESET_TEST_DATA_CACHE received!")
+                testDataCache.reset()
+            }
+            else -> Log.d(TAG, "Unknown action received!")
+        }
+    }
+
+    companion object {
+        private const val TAG = "FastPairTestDataManager"
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
similarity index 75%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
rename to nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
index f1a3863..cec0607 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
@@ -14,19 +14,39 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker.dataprovider
+package android.nearby.fastpair.seeker.dataprovider
 
 import android.accounts.Account
-import android.content.Intent
+import android.content.IntentFilter
 import android.nearby.FastPairDataProviderService
 import android.nearby.FastPairEligibleAccount
-import android.os.IBinder
+import android.nearby.fastpair.seeker.*
+import android.nearby.fastpair.seeker.data.FastPairTestDataManager
 import android.util.Log
 
 /**
  * Fast Pair Test Data Provider Service entry point for platform overlay.
  */
 class FastPairTestDataProviderService : FastPairDataProviderService(TAG) {
+    private lateinit var testDataManager: FastPairTestDataManager
+
+    override fun onCreate() {
+        Log.d(TAG, "onCreate()")
+        testDataManager = FastPairTestDataManager(this)
+
+        val bondStateFilter = IntentFilter(ACTION_RESET_TEST_DATA_CACHE).apply {
+            addAction(ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA)
+            addAction(ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA)
+        }
+        registerReceiver(testDataManager, bondStateFilter)
+    }
+
+    override fun onDestroy() {
+        Log.d(TAG, "onDestroy()")
+        unregisterReceiver(testDataManager)
+
+        super.onDestroy()
+    }
 
     override fun onLoadFastPairAntispoofKeyDeviceMetadata(
         request: FastPairAntispoofKeyDeviceMetadataRequest,
@@ -36,10 +56,11 @@
         Log.d(TAG, "onLoadFastPairAntispoofKeyDeviceMetadata(modelId: $requestedModelId)")
 
         val fastPairAntispoofKeyDeviceMetadata =
-            FastPairTestDataCache.antispoofKeyDeviceMetadataMap[requestedModelId]
+            testDataManager.testDataCache.getAntispoofKeyDeviceMetadata(requestedModelId)
         if (fastPairAntispoofKeyDeviceMetadata != null) {
             callback.onFastPairAntispoofKeyDeviceMetadataReceived(fastPairAntispoofKeyDeviceMetadata)
         } else {
+            Log.d(TAG, "No metadata available for $requestedModelId!")
             callback.onError(ERROR_CODE_BAD_REQUEST, "No metadata available for $requestedModelId")
         }
     }
@@ -54,10 +75,10 @@
             TAG, "onLoadFastPairAccountDevicesMetadata(" +
                     "account: $requestedAccount, accountKeys:$requestedAccountKeys)"
         )
-        Log.d(TAG, FastPairTestDataCache.dumpAccountKeyDeviceMetadata())
+        Log.d(TAG, testDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson())
 
         callback.onFastPairAccountDevicesMetadataReceived(
-            FastPairTestDataCache.accountKeyDeviceMetadata
+            testDataManager.testDataCache.getAccountKeyDeviceMetadataList()
         )
     }
 
@@ -95,7 +116,7 @@
         Log.d(TAG, "requestedBleAddress: $requestedBleAddress,")
         Log.d(TAG, "requestedAccountKeyDeviceMetadata: $requestedAccountKeyDeviceMetadata)")
 
-        FastPairTestDataCache.accountKeyDeviceMetadata += requestedAccountKeyDeviceMetadata
+        testDataManager.writeAccountKeyDeviceMetadata(requestedAccountKeyDeviceMetadata)
 
         callback.onSuccess()
     }
@@ -104,7 +125,7 @@
         private const val TAG = "FastPairTestDataProviderService"
         private val ELIGIBLE_ACCOUNTS_TEST_CONSTANT = listOf(
             FastPairEligibleAccount.Builder()
-                .setAccount(Account("nearby-mainline-fpseeker@google.com", "FakeTestAccount"))
+                .setAccount(Account(FAKE_TEST_ACCOUNT_NAME, "FakeTestAccount"))
                 .setOptIn(true)
                 .build(),
         )
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index 533153a..8c6559f 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -17,6 +17,7 @@
 }
 
 // Run the tests: atest -v CtsNearbyMultiDevicesTestSuite
+// TODO(b/223370810): Flash ROM with ag/17109260 reverted to pass Fast Pair scanning related tests.
 python_test_host {
     name: "CtsNearbyMultiDevicesTestSuite",
     main: "suite_main.py",
@@ -30,12 +31,16 @@
         unit_test: false,
     },
     data: [
-        // Package the snippet with the Mobly test
+        // Package the snippet with the Mobly test.
         ":NearbyMultiDevicesClientsSnippets",
+        // Package the data provider with the Mobly test.
+        ":NearbyFastPairSeekerDataProvider",
+        // Package the JSON metadata with the Mobly test.
+        "test_data/**/*",
     ],
 }
 
 python_library_host {
     name: "NearbyMultiDevicesHostHelper",
     srcs: ["test_helper/*.py"],
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index f8294f4..9453647 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -23,6 +23,7 @@
         -->
         <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
             <option name="test-file-name" value="NearbyMultiDevicesClientsSnippets.apk" />
+            <option name="test-file-name" value="NearbyFastPairSeekerDataProvider.apk" />
             <option name="check-min-sdk" value="true" />
         </target_preparer>
         <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -42,6 +43,7 @@
     <device name="device2">
         <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
             <option name="test-file-name" value="NearbyMultiDevicesClientsSnippets.apk" />
+            <option name="test-file-name" value="NearbyFastPairSeekerDataProvider.apk" />
             <option name="check-min-sdk" value="true" />
         </target_preparer>
         <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py b/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py
new file mode 100644
index 0000000..07079ae
--- /dev/null
+++ b/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py
@@ -0,0 +1,85 @@
+#  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.
+
+"""CTS-V Nearby Mainline Fast Pair end-to-end test case: seeker show half sheet UI."""
+
+from typing import List
+
+from mobly import base_test
+from mobly.controllers import android_device
+
+from test_helper import constants
+from test_helper import fast_pair_provider_simulator
+from test_helper import fast_pair_seeker
+
+# The model ID to simulate on provider side.
+PROVIDER_SIMULATOR_MODEL_ID = constants.DEFAULT_MODEL_ID
+# The public key to simulate as registered headsets.
+PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY = constants.DEFAULT_ANTI_SPOOFING_KEY
+# The anti-spoof key device metadata JSON file for data provider at seeker side.
+PROVIDER_SIMULATOR_KDM_JSON_FILE = constants.DEFAULT_KDM_JSON_FILE
+
+# Time in seconds for events waiting.
+SETUP_TIMEOUT_SEC = constants.SETUP_TIMEOUT_SEC
+BECOME_DISCOVERABLE_TIMEOUT_SEC = constants.BECOME_DISCOVERABLE_TIMEOUT_SEC
+START_ADVERTISING_TIMEOUT_SEC = constants.START_ADVERTISING_TIMEOUT_SEC
+HALF_SHEET_POPUP_TIMEOUT_SEC = constants.HALF_SHEET_POPUP_TIMEOUT_SEC
+
+# Abbreviations for common use type.
+FastPairProviderSimulator = fast_pair_provider_simulator.FastPairProviderSimulator
+FastPairSeeker = fast_pair_seeker.FastPairSeeker
+
+
+class SeekerShowHalfSheetTest(base_test.BaseTestClass):
+    """Fast Pair seeker show half sheet UI test."""
+
+    _duts: List[android_device.AndroidDevice]
+    _provider: FastPairProviderSimulator
+    _seeker: FastPairSeeker
+
+    def setup_class(self) -> None:
+        super().setup_class()
+        self._duts = self.register_controller(android_device)
+
+        # Assume the 1st phone is provider, the 2nd is seeker.
+        provider_ad, seeker_ad = self._duts
+        self._provider = FastPairProviderSimulator(provider_ad)
+        self._seeker = FastPairSeeker(seeker_ad)
+        self._provider.load_snippet()
+        self._seeker.load_snippet()
+
+    def setup_test(self) -> None:
+        super().setup_test()
+        self._provider.setup_provider_simulator(SETUP_TIMEOUT_SEC)
+        self._provider.start_model_id_advertising(PROVIDER_SIMULATOR_MODEL_ID,
+                                                  PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY)
+        self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
+        self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
+        self._seeker.put_anti_spoof_key_device_metadata(PROVIDER_SIMULATOR_MODEL_ID,
+                                                        PROVIDER_SIMULATOR_KDM_JSON_FILE)
+        self._seeker.set_fast_pair_scan_enabled(True)
+
+    def teardown_test(self) -> None:
+        super().teardown_test()
+        self._seeker.set_fast_pair_scan_enabled(False)
+        self._provider.teardown_provider_simulator()
+        self._seeker.dismiss_halfsheet()
+        # Create per-test excepts of logcat.
+        for dut in self._duts:
+            dut.services.create_output_excerpts_all(self.current_test_info)
+
+    def test_seeker_show_half_sheet(self) -> None:
+        self._seeker.wait_and_assert_halfsheet_showed(
+            timeout_seconds=HALF_SHEET_POPUP_TIMEOUT_SEC,
+            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID)
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
index 788fbd5..406a4f0 100644
--- a/nearby/tests/multidevices/host/suite_main.py
+++ b/nearby/tests/multidevices/host/suite_main.py
@@ -20,10 +20,12 @@
 from mobly import suite_runner
 
 import seeker_discover_provider_test
+import seeker_show_halfsheet_test
 
 _BOOTSTRAP_LOGGING_FILENAME = '/tmp/nearby_multi_devices_test_suite_log.txt'
 _TEST_CLASSES_LIST = [
     seeker_discover_provider_test.SeekerDiscoverProviderTest,
+    seeker_show_halfsheet_test.SeekerShowHalfSheetTest,
 ]
 
 
diff --git a/nearby/tests/multidevices/host/test_helper/constants.py b/nearby/tests/multidevices/host/test_helper/constants.py
index 6b5185f..413e70b 100644
--- a/nearby/tests/multidevices/host/test_helper/constants.py
+++ b/nearby/tests/multidevices/host/test_helper/constants.py
@@ -18,8 +18,12 @@
 # Default public key to simulate as registered headsets.
 DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
 
+# Default anti-spoof Key Device Metadata JSON file for data provider at seeker side.
+DEFAULT_KDM_JSON_FILE = 'simulator_antispoofkey_devicemeta_json.txt'
+
 # Time in seconds for events waiting.
 SETUP_TIMEOUT_SEC = 5
 BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
 START_ADVERTISING_TIMEOUT_SEC = 5
 SCAN_TIMEOUT_SEC = 30
+HALF_SHEET_POPUP_TIMEOUT_SEC = 30
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
index 9bb2689..cfdb966 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
@@ -19,6 +19,7 @@
 from mobly.controllers.android_device_lib import snippet_event
 
 from test_helper import event_helper
+from test_helper import utils
 
 # The package name of the Nearby Mainline Fast Pair seeker Mobly snippet.
 FP_SEEKER_SNIPPETS_PACKAGE = 'android.nearby.multidevices'
@@ -28,6 +29,7 @@
 
 # Abbreviations for common use type.
 AndroidDevice = android_device.AndroidDevice
+JsonObject = utils.JsonObject
 SnippetEvent = snippet_event.SnippetEvent
 wait_for_event = event_helper.wait_callback_event
 
@@ -104,3 +106,44 @@
             on_received=_on_provider_found_event_received,
             on_waiting=_on_provider_found_event_waiting,
             on_missed=_on_provider_found_event_missed)
+
+    def put_anti_spoof_key_device_metadata(self, model_id: str, kdm_json_file_name: str) -> None:
+        """Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
+
+        Args:
+          model_id: A string of model id to be associated with.
+          kdm_json_file_name: The FastPairAntispoofKeyDeviceMetadata JSON object.
+        """
+        self._ad.log.info('Puts FastPairAntispoofKeyDeviceMetadata into test data cache for '
+                          'model id "%s".', model_id)
+        kdm_json_object = utils.load_json_fast_pair_test_data(kdm_json_file_name)
+        self._ad.fp.putAntispoofKeyDeviceMetadata(
+            model_id,
+            utils.serialize_as_simplified_json_str(kdm_json_object))
+
+    def set_fast_pair_scan_enabled(self, enable: bool) -> None:
+        """Writes into Settings whether Fast Pair scan is enabled.
+
+        Args:
+          enable: whether the Fast Pair scan should be enabled.
+        """
+        self._ad.log.info('%s Fast Pair scan in Android settings.',
+                          'Enables' if enable else 'Disables')
+        self._ad.fp.setFastPairScanEnabled(enable)
+
+    def wait_and_assert_halfsheet_showed(self, timeout_seconds: int,
+                                         expected_model_id: str) -> None:
+        """Waits and asserts the onHalfSheetShowed event from the seeker.
+
+        Args:
+          timeout_seconds: The number of seconds to wait before giving up.
+          expected_model_id: The expected model ID of the remote Fast Pair provider
+            device.
+        """
+        self._ad.log.info('Waits and asserts the half sheet showed for model id "%s".',
+                          expected_model_id)
+        self._ad.fp.waitAndAssertHalfSheetShowed(expected_model_id, timeout_seconds)
+
+    def dismiss_halfsheet(self) -> None:
+        """Dismisses the half sheet UI if showed."""
+        self._ad.fp.dismissHalfSheet()
diff --git a/nearby/tests/multidevices/host/test_helper/utils.py b/nearby/tests/multidevices/host/test_helper/utils.py
new file mode 100644
index 0000000..a0acb57
--- /dev/null
+++ b/nearby/tests/multidevices/host/test_helper/utils.py
@@ -0,0 +1,42 @@
+#  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.
+
+import json
+import pathlib
+import sys
+from typing import Any, Dict
+
+# Type definition
+JsonObject = Dict[str, Any]
+
+
+def load_json_fast_pair_test_data(json_file_name: str) -> JsonObject:
+    """Loads a JSON text file from test data directory into a Json object.
+
+    Args:
+      json_file_name: The name of the JSON file.
+    """
+    return json.loads(
+        pathlib.Path(sys.argv[0]).parent.joinpath(
+            'test_data', 'fastpair', json_file_name).read_text()
+    )
+
+
+def serialize_as_simplified_json_str(json_data: JsonObject) -> str:
+    """Serializes a JSON object into a string without empty space.
+
+    Args:
+      json_data: The JSON object to be serialized.
+    """
+    return json.dumps(json_data, separators=(',', ':'))