Refactor snippet RPC methods and events.

1. start/stop ProviderSimulator change to setup/teardown
2. Do not auto start FastPairSimulator in onA2DPSinkProfileConnected callback
3. Add onA2DPSinkProfileConnected event

Test: atest -v CtsSeekerDiscoverProviderTest
BUG: 214015364
Change-Id: I40289fb48919b0890c230b991bc4a0a34a5669d1
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt
index 0eacb71..e700144 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt
@@ -23,62 +23,99 @@
 import com.google.android.mobly.snippet.util.Log
 import com.google.common.io.BaseEncoding
 
-class FastPairProviderSimulatorController(
-    private val context: Context,
-    private val modelId: String,
-    private val antiSpoofingKeyString: String,
-    private val eventListener: EventListener,
-) : BluetoothController.EventListener {
+class FastPairProviderSimulatorController(private val context: Context) :
+    FastPairSimulator.AdvertisingChangedCallback, BluetoothController.EventListener {
     private lateinit var bluetoothController: BluetoothController
-    lateinit var simulator: FastPairSimulator
+    private lateinit var eventListener: EventListener
+    private var simulator: FastPairSimulator? = null
 
-    fun startProviderSimulator() {
+    fun setupProviderSimulator(listener: EventListener) {
+        eventListener = listener
+
         bluetoothController = BluetoothController(context, this)
         bluetoothController.registerBluetoothStateReceiver()
         bluetoothController.enableBluetooth()
         bluetoothController.connectA2DPSinkProfile()
     }
 
-    fun stopProviderSimulator() {
-        simulator.destroy()
+    fun teardownProviderSimulator() {
+        simulator?.destroy()
         bluetoothController.unregisterBluetoothStateReceiver()
     }
 
-    override fun onA2DPSinkProfileConnected() {
-        createFastPairSimulator()
+    fun startModelIdAdvertising(
+        modelId: String,
+        antiSpoofingKeyString: String,
+        listener: EventListener
+    ) {
+        eventListener = listener
+
+        val antiSpoofingKey = BaseEncoding.base64().decode(antiSpoofingKeyString)
+        simulator = FastPairSimulator(
+            context, FastPairSimulator.Options.builder(modelId)
+                .setAdvertisingModelId(modelId)
+                .setBluetoothAddress(null)
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+                .setAdvertisingChangedCallback(this)
+                .setAntiSpoofingPrivateKey(antiSpoofingKey)
+                .setUseRandomSaltForAccountKeyRotation(false)
+                .setDataOnlyConnection(false)
+                .setShowsPasskeyConfirmation(false)
+                .setRemoveAllDevicesDuringPairing(true)
+                .build()
+        )
+
+        // TODO(b/222070055): Workaround the FATAL EXCEPTION after the end of initial pairing.
+        simulator!!.setSuppressSubsequentPairingNotification(true)
     }
 
+    fun getProviderSimulatorBleAddress() = simulator!!.bleAddress!!
+
+    /**
+     * Called when we change our BLE advertisement.
+     *
+     * @param isAdvertising the advertising status.
+     */
+    override fun onAdvertisingChanged(isAdvertising: Boolean) {
+        Log.i("FastPairSimulator onAdvertisingChanged(isAdvertising: $isAdvertising)")
+        eventListener.onAdvertisingChange(isAdvertising)
+    }
+
+    /** The callback for the first onServiceConnected of A2DP sink profile. */
+    override fun onA2DPSinkProfileConnected() {
+        eventListener.onA2DPSinkProfileConnected()
+    }
+
+    /**
+     * Reports the current bond state of the remote device.
+     *
+     * @param bondState the bond state of the remote device.
+     */
     override fun onBondStateChanged(bondState: Int) {
     }
 
+    /**
+     * Reports the current connection state of the remote device.
+     *
+     * @param connectionState the bond state of the remote device.
+     */
     override fun onConnectionStateChanged(connectionState: Int) {
     }
 
+    /**
+     * Reports the current scan mode of the local Adapter.
+     *
+     * @param mode the current scan mode of the local Adapter.
+     */
     override fun onScanModeChange(mode: Int) {
         eventListener.onScanModeChange(FastPairSimulator.scanModeToString(mode))
     }
 
-    private fun createFastPairSimulator() {
-        val antiSpoofingKey = BaseEncoding.base64().decode(antiSpoofingKeyString)
-        simulator = FastPairSimulator(context, FastPairSimulator.Options.builder(modelId)
-            .setAdvertisingModelId(modelId)
-            .setBluetoothAddress(null)
-            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-            .setAdvertisingChangedCallback {
-                val isAdvertising = simulator.isAdvertising
-                Log.i("FastPairSimulator callback(), isAdvertising: $isAdvertising")
-                eventListener.onAdvertisingChange(isAdvertising)
-            }
-            .setAntiSpoofingPrivateKey(antiSpoofingKey)
-            .setUseRandomSaltForAccountKeyRotation(false)
-            .setDataOnlyConnection(false)
-            .setShowsPasskeyConfirmation(false)
-            .setRemoveAllDevicesDuringPairing(true)
-            .build())
-    }
-
     /** Interface for listening the events from Fast Pair Provider Simulator. */
     interface EventListener {
+        /** Reports the first onServiceConnected of A2DP sink profile. */
+        fun onA2DPSinkProfileConnected()
+
         /**
          * Reports the current scan mode of the local Adapter.
          *
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 356823e..39edfe4 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
@@ -28,33 +28,44 @@
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 class FastPairProviderSimulatorSnippet : Snippet {
     private val context: Context = InstrumentationRegistry.getInstrumentation().context
-    private lateinit var fastPairProviderSimulatorController: FastPairProviderSimulatorController
+    private val fastPairProviderSimulatorController = FastPairProviderSimulatorController(context)
+
+    /** Sets up the Fast Pair provider simulator. */
+    @AsyncRpc(description = "Sets up FP provider simulator.")
+    fun setupProviderSimulator(callbackId: String) {
+        fastPairProviderSimulatorController.setupProviderSimulator(ProviderStatusEvents(callbackId))
+    }
 
     /**
-     * Starts the Fast Pair provider simulator.
+     * Starts model id advertising for scanning and initial pairing.
      *
      * @param callbackId the callback ID corresponding to the
      * [FastPairProviderSimulatorSnippet#startProviderSimulator] call that started the scanning.
      * @param modelId a 3-byte hex string for seeker side to recognize the device (ex: 0x00000C).
      * @param antiSpoofingKeyString a public key for registered headsets.
      */
-    @AsyncRpc(description = "Starts FP provider simulator for seekers to discover.")
-    fun startProviderSimulator(callbackId: String, modelId: String, antiSpoofingKeyString: String) {
-        fastPairProviderSimulatorController = FastPairProviderSimulatorController(
-            context, modelId, antiSpoofingKeyString, ProviderStatusEvents(callbackId)
+    @AsyncRpc(description = "Starts model id advertising for scanning and initial pairing.")
+    fun startModelIdAdvertising(
+        callbackId: String,
+        modelId: String,
+        antiSpoofingKeyString: String
+    ) {
+        fastPairProviderSimulatorController.startModelIdAdvertising(
+            modelId,
+            antiSpoofingKeyString,
+            ProviderStatusEvents(callbackId)
         )
-        fastPairProviderSimulatorController.startProviderSimulator()
     }
 
-    /** Stops the Fast Pair provider simulator. */
-    @Rpc(description = "Stops FP provider simulator.")
-    fun stopProviderSimulator() {
-        fastPairProviderSimulatorController.stopProviderSimulator()
+    /** Tears down the Fast Pair provider simulator. */
+    @Rpc(description = "Tears down FP provider simulator.")
+    fun teardownProviderSimulator() {
+        fastPairProviderSimulatorController.teardownProviderSimulator()
     }
 
     /** Gets BLE mac address of the Fast Pair provider simulator. */
     @Rpc(description = "Gets BLE mac address of the Fast Pair provider simulator.")
     fun getBluetoothLeAddress(): String {
-        return fastPairProviderSimulatorController.simulator.bleAddress!!
+        return fastPairProviderSimulatorController.getProviderSimulatorBleAddress()
     }
 }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
index 20983d3..efa4f02 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
@@ -22,6 +22,11 @@
 class ProviderStatusEvents(private val callbackId: String) :
   FastPairProviderSimulatorController.EventListener {
 
+  /** Reports the first onServiceConnected of A2DP sink profile. */
+  override fun onA2DPSinkProfileConnected() {
+    postSnippetEvent(callbackId, "onA2DPSinkProfileConnected") {}
+  }
+
   /**
    * Indicates the Bluetooth scan mode of the Fast Pair provider simulator has changed.
    *
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
index 97f3bf4..e916c53 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
@@ -671,7 +671,7 @@
                                 mAppLaunchSwitch.isChecked() ? MODEL_ID_APP_LAUNCH : modelId)
                         .setBluetoothAddress(finalBluetoothAddress)
                         .setTxPowerLevel(toTxPowerLevel(txPower))
-                        .setAdvertisingChangedCallback(this::updateStatusView)
+                        .setAdvertisingChangedCallback(isAdvertising -> updateStatusView())
                         .setAntiSpoofingPrivateKey(antiSpoofingKey)
                         .setUseRandomSaltForAccountKeyRotation(useRandomSaltForAccountKeyRotation)
                         .setDataOnlyConnection(device != null && device.getDataOnlyConnection())
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
index aa7daa6..232e84b 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
@@ -572,8 +572,12 @@
 
     /** An optional way to get advertising status updates. */
     public interface AdvertisingChangedCallback {
-        /** Called when we change our BLE advertisement. */
-        void onAdvertisingChanged();
+        /**
+         * Called when we change our BLE advertisement.
+         *
+         * @param isAdvertising the advertising status.
+         */
+        void onAdvertisingChanged(boolean isAdvertising);
     }
 
     /** A way for tests to get callbacks when passkey confirmation is invoked. */
@@ -758,7 +762,7 @@
                     .setModelId(Ascii.toUpperCase(modelId))
                     .setAdvertisingModelId(Ascii.toUpperCase(modelId))
                     .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                    .setAdvertisingChangedCallback(() -> {
+                    .setAdvertisingChangedCallback(isAdvertising -> {
                     })
                     .setIncludeTransportDataDescriptor(true)
                     .setUseRandomSaltForAccountKeyRotation(false)
@@ -1077,7 +1081,7 @@
     public void setIsAdvertising(boolean isAdvertising) {
         if (this.mIsAdvertising != isAdvertising) {
             this.mIsAdvertising = isAdvertising;
-            mOptions.getAdvertisingChangedCallback().onAdvertisingChanged();
+            mOptions.getAdvertisingChangedCallback().onAdvertisingChanged(isAdvertising);
         }
     }
 
diff --git a/nearby/tests/multidevices/host/fast_pair_provider_simulator.py b/nearby/tests/multidevices/host/fast_pair_provider_simulator.py
index 1f62dfb..37ba387 100644
--- a/nearby/tests/multidevices/host/fast_pair_provider_simulator.py
+++ b/nearby/tests/multidevices/host/fast_pair_provider_simulator.py
@@ -11,6 +11,7 @@
 FP_PROVIDER_SIMULATOR_SNIPPETS_PACKAGE = 'android.nearby.multidevices'
 
 # Events reported from the provider simulator snippet.
+ON_A2DP_SINK_PROFILE_CONNECT_EVENT = 'onA2DPSinkProfileConnected'
 ON_SCAN_MODE_CHANGE_EVENT = 'onScanModeChange'
 ON_ADVERTISING_CHANGE_EVENT = 'onAdvertisingChange'
 
@@ -39,21 +40,50 @@
         self._ad.load_snippet(
             name='fp', package=FP_PROVIDER_SIMULATOR_SNIPPETS_PACKAGE)
 
-    def start_provider_simulator(self, model_id: str,
-                                 anti_spoofing_key: str) -> None:
-        """Starts the Fast Pair provider simulator.
+    def setup_provider_simulator(self, timeout_seconds: int) -> None:
+        """Sets up the Fast Pair provider simulator.
+
+        Args:
+          timeout_seconds: The number of seconds to wait before giving up.
+        """
+        setup_status_callback = self._ad.fp.setupProviderSimulator()
+
+        def _on_a2dp_sink_profile_connect_event_received(_, elapsed_time: int) -> bool:
+            self._ad.log.info('Provider simulator connected to A2DP sink in %d seconds.',
+                              elapsed_time)
+            return True
+
+        def _on_a2dp_sink_profile_connect_event_waiting(elapsed_time: int) -> None:
+            self._ad.log.info(
+                'Still waiting "%s" event callback from provider side '
+                'after %d seconds...', ON_A2DP_SINK_PROFILE_CONNECT_EVENT, elapsed_time)
+
+        def _on_a2dp_sink_profile_connect_event_missed() -> None:
+            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
+                         f'the specific "{ON_A2DP_SINK_PROFILE_CONNECT_EVENT}" event.')
+
+        wait_for_event(
+            callback_event_handler=setup_status_callback,
+            event_name=ON_A2DP_SINK_PROFILE_CONNECT_EVENT,
+            timeout_seconds=timeout_seconds,
+            on_received=_on_a2dp_sink_profile_connect_event_received,
+            on_waiting=_on_a2dp_sink_profile_connect_event_waiting,
+            on_missed=_on_a2dp_sink_profile_connect_event_missed)
+
+    def start_model_id_advertising(self, model_id: str, anti_spoofing_key: str) -> None:
+        """Starts model id advertising for scanning and initial pairing.
 
         Args:
           model_id: A 3-byte hex string for seeker side to recognize the device (ex:
             0x00000C).
           anti_spoofing_key: A public key for registered headsets.
         """
-        self._provider_status_callback = self._ad.fp.startProviderSimulator(
-            model_id, anti_spoofing_key)
+        self._provider_status_callback = (
+            self._ad.fp.startModelIdAdvertising(model_id, anti_spoofing_key))
 
-    def stop_provider_simulator(self) -> None:
-        """Stops the Fast Pair provider simulator."""
-        self._ad.fp.stopProviderSimulator()
+    def teardown_provider_simulator(self) -> None:
+        """Tears down the Fast Pair provider simulator."""
+        self._ad.fp.teardownProviderSimulator()
 
     @retry.retry(tries=3)
     def get_ble_mac_address(self) -> str:
diff --git a/nearby/tests/multidevices/host/seeker_discover_provider_test.py b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
index a52ca15..32976c1 100644
--- a/nearby/tests/multidevices/host/seeker_discover_provider_test.py
+++ b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
@@ -17,6 +17,7 @@
 # Default public key to simulate as registered headsets.
 DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
 # Time in seconds for events waiting.
+SETUP_TIMEOUT_SEC = 5
 BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
 START_ADVERTISING_TIMEOUT_SEC = 5
 SCAN_TIMEOUT_SEC = 30
@@ -47,8 +48,8 @@
 
     def setup_test(self) -> None:
         super().setup_test()
-        self._provider.start_provider_simulator(DEFAULT_MODEL_ID,
-                                                DEFAULT_ANTI_SPOOFING_KEY)
+        self._provider.setup_provider_simulator(SETUP_TIMEOUT_SEC)
+        self._provider.start_model_id_advertising(DEFAULT_MODEL_ID, DEFAULT_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.start_scan()
@@ -56,7 +57,7 @@
     def teardown_test(self) -> None:
         super().teardown_test()
         self._seeker.stop_scan()
-        self._provider.stop_provider_simulator()
+        self._provider.teardown_provider_simulator()
         # Create per-test excepts of logcat.
         for dut in self.duts:
             dut.services.create_output_excerpts_all(self.current_test_info)