Add new e2e test case: initial_pairing_test.

This test verify initial pairing between mainline seeker and
provider simulator. The "pair" is triggered by halfsheet UI.
Test pass if account key device saved on seeker side match
with the one wrote at provider side.

Test: atest -v CtsNearbyMultiDevicesTestSuite
Test: http://recall/-/cycSROwIosTzHgYDSIqPXl/fteYnrnJAwKqYYewUDPUxz
Bug: 214015364
Ignore-AOSP-First: nearby_not_in_aosp_yet
Change-Id: Ida5ba2eea527d1662209a6f60b547398dcdc14e9
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
index 4a8a772..922e950 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
@@ -70,4 +70,10 @@
     fun getBluetoothLeAddress(): String {
         return fastPairProviderSimulatorController.getProviderSimulatorBleAddress()
     }
+
+    /** Gets the latest account key received on the Fast Pair provider simulator */
+    @Rpc(description = "Gets the latest account key received on the Fast Pair provider simulator.")
+    fun getLatestReceivedAccountKey(): String? {
+        return fastPairProviderSimulatorController.getLatestReceivedAccountKey()
+    }
 }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
index 2ab6dbd..a2d2659 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
@@ -21,7 +21,7 @@
 import android.nearby.fastpair.provider.FastPairSimulator
 import android.nearby.fastpair.provider.bluetooth.BluetoothController
 import com.google.android.mobly.snippet.util.Log
-import com.google.common.io.BaseEncoding
+import com.google.common.io.BaseEncoding.base64
 
 class FastPairProviderSimulatorController(private val context: Context) :
     FastPairSimulator.AdvertisingChangedCallback, BluetoothController.EventListener {
@@ -50,7 +50,7 @@
     ) {
         eventListener = listener
 
-        val antiSpoofingKey = BaseEncoding.base64().decode(antiSpoofingKeyString)
+        val antiSpoofingKey = base64().decode(antiSpoofingKeyString)
         simulator = FastPairSimulator(
             context, FastPairSimulator.Options.builder(modelId)
                 .setAdvertisingModelId(modelId)
@@ -68,6 +68,9 @@
 
     fun getProviderSimulatorBleAddress() = simulator!!.bleAddress!!
 
+    fun getLatestReceivedAccountKey() =
+        simulator!!.accountKey?.let { base64().encode(it.toByteArray()) }
+
     /**
      * Called when we change our BLE advertisement.
      *
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 65856d8..fd4f4b4 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
@@ -23,9 +23,11 @@
 import android.nearby.ScanRequest
 import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
 import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
+import android.nearby.multidevices.fastpair.seeker.events.PairingCallbackEvents
 import android.nearby.multidevices.fastpair.seeker.events.ScanCallbackEvents
 import android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest
 import android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest
+import android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest
 import androidx.test.core.app.ApplicationProvider
 import com.google.android.mobly.snippet.Snippet
 import com.google.android.mobly.snippet.rpc.AsyncRpc
@@ -114,7 +116,7 @@
     @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.")
-        fastPairTestDataManager.sendAccountKeyDeviceMetadata(json)
+        fastPairTestDataManager.sendAccountKeyDeviceMetadataJsonArray(json)
     }
 
     /** Dumps all FastPairAccountKeyDeviceMetadata from the test data cache. */
@@ -142,11 +144,25 @@
         DismissNearbyHalfSheetUiTest().dismissHalfSheet()
     }
 
+    /** Starts pairing by interacting with half sheet UI.
+     *
+     * @param callbackId the callback ID corresponding to the
+     * {@link FastPairSeekerSnippet#startPairing} call that started the pairing.
+     */
+    @AsyncRpc(description = "Starts pairing by interacting with half sheet UI.")
+    fun startPairing(callbackId: String) {
+        Log.i("Starts pairing by interacting with half sheet UI.")
+
+        PairByNearbyHalfSheetUiTest().clickConnectButton()
+        fastPairTestDataManager.registerDataReceiveListener(PairingCallbackEvents(callbackId))
+    }
+
     /** Invokes when the snippet runner shutting down. */
     override fun shutdown() {
         super.shutdown()
 
         Log.i("Resets the Fast Pair test data cache.")
+        fastPairTestDataManager.unregisterDataReceiveListener()
         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
index 291aad8..239ac61 100644
--- 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
@@ -19,12 +19,20 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
-import android.nearby.fastpair.seeker.*
+import android.content.IntentFilter
+import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE
+import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
+import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
+import android.nearby.fastpair.seeker.ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA
+import android.nearby.fastpair.seeker.DATA_JSON_STRING_KEY
+import android.nearby.fastpair.seeker.DATA_MODEL_ID_STRING_KEY
+import android.nearby.fastpair.seeker.FastPairTestDataCache
 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()
+    var listener: EventListener? = null
 
     /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into local and remote cache.
      *
@@ -41,17 +49,17 @@
         testDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
     }
 
-    /** Puts account key device metadata to local and remote cache.
+    /** Puts account key device metadata array to local and remote cache.
      *
      * @param json a string of FastPairAccountKeyDeviceMetadata JSON array.
      */
-    fun sendAccountKeyDeviceMetadata(json: String) {
+    fun sendAccountKeyDeviceMetadataJsonArray(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)
+        testDataCache.putAccountKeyDeviceMetadataJsonArray(json)
     }
 
     /** Clears local and remote cache. */
@@ -73,12 +81,33 @@
             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)
+                testDataCache.putAccountKeyDeviceMetadataJsonObject(json)
+                listener?.onManageFastPairAccountDevice(json)
             }
             else -> Log.d(TAG, "Unknown action received!")
         }
     }
 
+    fun registerDataReceiveListener(listener: EventListener) {
+        this.listener = listener
+        val bondStateFilter = IntentFilter(ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA)
+        context.registerReceiver(this, bondStateFilter)
+    }
+
+    fun unregisterDataReceiveListener() {
+        this.listener = null
+        context.unregisterReceiver(this)
+    }
+
+    /** Interface for listening the data receive from the remote cache in data provider. */
+    interface EventListener {
+        /** Reports a FastPairAccountKeyDeviceMetadata write into the cache.
+         *
+         * @param json the FastPairAccountKeyDeviceMetadata as JSON object string.
+         */
+        fun onManageFastPairAccountDevice(json: String)
+    }
+
     companion object {
         private const val TAG = "FastPairTestDataManager"
     }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt
new file mode 100644
index 0000000..19de1d9
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.events
+
+import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
+import com.google.android.mobly.snippet.util.postSnippetEvent
+
+/** The Mobly snippet events to report to the Python side. */
+class PairingCallbackEvents(private val callbackId: String) :
+    FastPairTestDataManager.EventListener {
+
+    /** Reports a FastPairAccountKeyDeviceMetadata write into the cache.
+     *
+     * @param json the FastPairAccountKeyDeviceMetadata as JSON object string.
+     */
+    override fun onManageFastPairAccountDevice(json: String) {
+        postSnippetEvent(callbackId, "onManageAccountDevice") {
+            putString("accountDeviceJsonString", json)
+        }
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..9028668
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.test.uiautomator.Until
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest \
+ * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class PairByNearbyHalfSheetUiTest {
+    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @Test
+    fun clickConnectButton() {
+        val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
+        device.findObject(connectButton).click()
+        device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
+    }
+
+    companion object {
+        const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
index be94031..e08a122 100644
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
@@ -31,12 +31,18 @@
     private val antispoofKeyDeviceMetadataDataMap =
         mutableMapOf<String, FastPairAntispoofKeyDeviceMetadataData>()
 
-    fun putAccountKeyDeviceMetadata(json: String) {
+    fun putAccountKeyDeviceMetadataJsonArray(json: String) {
         accountKeyDeviceMetadataList +=
             gson.fromJson(json, Array<FastPairAccountKeyDeviceMetadataData>::class.java)
                 .map { it.toFastPairAccountKeyDeviceMetadata() }
     }
 
+    fun putAccountKeyDeviceMetadataJsonObject(json: String) {
+        accountKeyDeviceMetadataList +=
+            gson.fromJson(json, FastPairAccountKeyDeviceMetadataData::class.java)
+                .toFastPairAccountKeyDeviceMetadata()
+    }
+
     fun putAccountKeyDeviceMetadata(accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata) {
         accountKeyDeviceMetadataList += accountKeyDeviceMetadata
     }
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
index f226789..e924da1 100644
--- 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
@@ -72,7 +72,7 @@
             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)
+                testDataCache.putAccountKeyDeviceMetadataJsonArray(json)
             }
             ACTION_RESET_TEST_DATA_CACHE -> {
                 Log.d(TAG, "ACTION_RESET_TEST_DATA_CACHE received!")
diff --git a/nearby/tests/multidevices/host/initial_pairing_test.py b/nearby/tests/multidevices/host/initial_pairing_test.py
new file mode 100644
index 0000000..1a49045
--- /dev/null
+++ b/nearby/tests/multidevices/host/initial_pairing_test.py
@@ -0,0 +1,62 @@
+#  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: initial pairing test."""
+
+from test_helper import constants
+from test_helper import fast_pair_base_test
+
+# 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
+MANAGE_ACCOUNT_DEVICE_TIMEOUT_SEC = constants.AVERAGE_PAIRING_TIMEOUT_SEC * 2
+
+
+class InitialPairingTest(fast_pair_base_test.FastPairBaseTest):
+    """Fast Pair initial pairing test."""
+
+    def setup_test(self) -> None:
+        super().setup_test()
+        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)
+
+    # TODO(b/214015364): Remove Bluetooth bound on both sides ("Forget device").
+    def teardown_test(self) -> None:
+        self._seeker.set_fast_pair_scan_enabled(False)
+        self._provider.teardown_provider_simulator()
+        self._seeker.dismiss_halfsheet()
+        super().teardown_test()
+
+    def test_seeker_initial_pair_provider(self) -> None:
+        self._seeker.wait_and_assert_halfsheet_showed(
+            timeout_seconds=HALF_SHEET_POPUP_TIMEOUT_SEC,
+            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID)
+        self._seeker.start_pairing()
+        self._seeker.wait_and_assert_account_device(
+            get_account_key_from_provider=self._provider.get_latest_received_account_key,
+            timeout_seconds=MANAGE_ACCOUNT_DEVICE_TIMEOUT_SEC)
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
index 406a4f0..4f5d48c 100644
--- a/nearby/tests/multidevices/host/suite_main.py
+++ b/nearby/tests/multidevices/host/suite_main.py
@@ -19,6 +19,7 @@
 
 from mobly import suite_runner
 
+import initial_pairing_test
 import seeker_discover_provider_test
 import seeker_show_halfsheet_test
 
@@ -26,6 +27,7 @@
 _TEST_CLASSES_LIST = [
     seeker_discover_provider_test.SeekerDiscoverProviderTest,
     seeker_show_halfsheet_test.SeekerShowHalfSheetTest,
+    initial_pairing_test.InitialPairingTest,
 ]
 
 
diff --git a/nearby/tests/multidevices/host/test_helper/constants.py b/nearby/tests/multidevices/host/test_helper/constants.py
index 646b428..342be8f 100644
--- a/nearby/tests/multidevices/host/test_helper/constants.py
+++ b/nearby/tests/multidevices/host/test_helper/constants.py
@@ -21,12 +21,14 @@
 # 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.
+# Time in seconds for events waiting according to Fast Pair certification guidelines:
+# https://developers.google.com/nearby/fast-pair/certification-guideline
 SETUP_TIMEOUT_SEC = 5
 BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
 START_ADVERTISING_TIMEOUT_SEC = 5
-SCAN_TIMEOUT_SEC = 30
-HALF_SHEET_POPUP_TIMEOUT_SEC = 30
+SCAN_TIMEOUT_SEC = 5
+HALF_SHEET_POPUP_TIMEOUT_SEC = 5
+AVERAGE_PAIRING_TIMEOUT_SEC = 12
 
 # The phone to simulate Fast Pair provider (like headphone) needs changes in Android system:
 # 1. System permission check removal
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
index 8a98112..d6484fb 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
@@ -18,6 +18,7 @@
 from mobly.controllers import android_device
 from mobly.controllers.android_device_lib import snippet_event
 import retry
+from typing import Optional
 
 from test_helper import event_helper
 
@@ -179,3 +180,11 @@
             on_received=_on_advertising_mode_change_event_received,
             on_waiting=_on_advertising_mode_change_event_waiting,
             on_missed=_on_advertising_mode_change_event_missed)
+
+    def get_latest_received_account_key(self) -> Optional[str]:
+        """Gets the latest account key received on the provider side.
+
+        Returns:
+          The account key received at provider side.
+        """
+        return self._ad.fp.getLatestReceivedAccountKey()
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 cfdb966..64fc2f2 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
@@ -14,6 +14,9 @@
 
 """Fast Pair seeker role."""
 
+import json
+from typing import Callable, Optional
+
 from mobly import asserts
 from mobly.controllers import android_device
 from mobly.controllers.android_device_lib import snippet_event
@@ -26,10 +29,12 @@
 
 # Events reported from the seeker snippet.
 ON_PROVIDER_FOUND_EVENT = 'onDiscovered'
+ON_MANAGE_ACCOUNT_DEVICE_EVENT = 'onManageAccountDevice'
 
 # Abbreviations for common use type.
 AndroidDevice = android_device.AndroidDevice
 JsonObject = utils.JsonObject
+ProviderAccountKeyCallable = Callable[[], Optional[str]]
 SnippetEvent = snippet_event.SnippetEvent
 wait_for_event = event_helper.wait_callback_event
 
@@ -41,6 +46,7 @@
         self._ad = ad
         self._ad.debug_tag = 'MainlineFastPairSeeker'
         self._scan_result_callback = None
+        self._pairing_result_callback = None
 
     def load_snippet(self) -> None:
         """Starts the seeker snippet and connects.
@@ -58,18 +64,6 @@
         """Stops the Fast Pair seeker scanning."""
         self._ad.fp.stopScan()
 
-    def start_pair(self, model_id: str, address: str) -> None:
-        """Starts the Fast Pair seeker pairing.
-
-        Args:
-          model_id: A 3-byte hex string for seeker side to recognize the provider
-            device (ex: 0x00000C).
-          address: The BLE mac address of the Fast Pair provider.
-        """
-        self._ad.log.info('Before calling startPairing')
-        self._ad.fp.startPairing(model_id, address)
-        self._ad.log.info('After calling startPairing')
-
     def wait_and_assert_provider_found(self, timeout_seconds: int,
                                        expected_model_id: str,
                                        expected_ble_mac_address: str) -> None:
@@ -137,8 +131,8 @@
 
         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.
+          expected_model_id: A 3-byte hex string for seeker side to recognize
+            the remote provider device (ex: 0x00000c).
         """
         self._ad.log.info('Waits and asserts the half sheet showed for model id "%s".',
                           expected_model_id)
@@ -147,3 +141,46 @@
     def dismiss_halfsheet(self) -> None:
         """Dismisses the half sheet UI if showed."""
         self._ad.fp.dismissHalfSheet()
+
+    def start_pairing(self) -> None:
+        """Starts pairing the provider via "Connect" button on half sheet UI."""
+        self._pairing_result_callback = self._ad.fp.startPairing()
+
+    def wait_and_assert_account_device(
+            self, timeout_seconds: int,
+            get_account_key_from_provider: ProviderAccountKeyCallable) -> None:
+        """Waits and asserts the onHalfSheetShowed event from the seeker.
+
+        Args:
+          timeout_seconds: The number of seconds to wait before giving up.
+          get_account_key_from_provider: The callable to get expected account key from the provider
+            side.
+        """
+
+        def _on_manage_account_device_event_received(manage_account_device_event: SnippetEvent,
+                                                     elapsed_time: int) -> bool:
+            account_key_json_str = manage_account_device_event.data['accountDeviceJsonString']
+            account_key_from_seeker = json.loads(account_key_json_str)['account_key']
+            account_key_from_provider = get_account_key_from_provider()
+            self._ad.log.info('Seeker add an account device with account key "%s" in %d seconds.',
+                              account_key_from_seeker, elapsed_time)
+            self._ad.log.info('The latest provider side account key is "%s".',
+                              account_key_from_provider)
+            return account_key_from_seeker == account_key_from_provider
+
+        def _on_manage_account_device_event_waiting(elapsed_time: int) -> None:
+            self._ad.log.info(
+                'Still waiting "%s" event callback from seeker side '
+                'after %d seconds...', ON_MANAGE_ACCOUNT_DEVICE_EVENT, elapsed_time)
+
+        def _on_manage_account_device_event_missed() -> None:
+            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
+                         f'the specific "{ON_MANAGE_ACCOUNT_DEVICE_EVENT}" event.')
+
+        wait_for_event(
+            callback_event_handler=self._pairing_result_callback,
+            event_name=ON_MANAGE_ACCOUNT_DEVICE_EVENT,
+            timeout_seconds=timeout_seconds,
+            on_received=_on_manage_account_device_event_received,
+            on_waiting=_on_manage_account_device_event_waiting,
+            on_missed=_on_manage_account_device_event_missed)