Send OffloadServiceInfos when registerOffloadEngine is called

When the OffloadEngine is registered, it should get notice about all the
currently offloaded service.

Bug: 297314970
Test: atest CtsNetTestCases
Change-Id: I1b977a40476a1f5c30a075b559e72656a42865ae
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index eb73ee5..69d5c5b 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1162,7 +1162,7 @@
                         // TODO: Limits the number of registrations created by a given class.
                         mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
                                 offloadEngineInfo);
-                        // TODO: Sends all the existing OffloadServiceInfos back.
+                        sendAllOffloadServiceInfos(offloadEngineInfo);
                         break;
                     case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
                         mOffloadEngines.unregister((IOffloadEngine) msg.obj);
@@ -1834,6 +1834,21 @@
         }
     }
 
+    private void sendAllOffloadServiceInfos(@NonNull OffloadEngineInfo offloadEngineInfo) {
+        final String targetInterface = offloadEngineInfo.mInterfaceName;
+        final IOffloadEngine offloadEngine = offloadEngineInfo.mOffloadEngine;
+        final List<MdnsAdvertiser.OffloadServiceInfoWrapper> offloadWrappers =
+                mAdvertiser.getAllInterfaceOffloadServiceInfos(targetInterface);
+        for (MdnsAdvertiser.OffloadServiceInfoWrapper wrapper : offloadWrappers) {
+            try {
+                offloadEngine.onOffloadServiceUpdated(wrapper.mOffloadServiceInfo);
+            } catch (RemoteException e) {
+                // Can happen in regular cases, do not log a stacktrace
+                Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
+            }
+        }
+    }
+
     private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
             @NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
         final int count = mOffloadEngines.beginBroadcast();
@@ -1857,7 +1872,7 @@
                     }
                 } catch (RemoteException e) {
                     // Can happen in regular cases, do not log a stacktrace
-                    Log.i(TAG, "Failed to send offload callback, remote died", e);
+                    Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
                 }
             }
         } finally {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index dd72d11..f43df45 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -37,6 +37,7 @@
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -117,6 +118,17 @@
         }
     }
 
+    /**
+     * Gets the current status of the OffloadServiceInfos per interface.
+     * @param interfaceName the target interfaceName
+     * @return the list of current offloaded services.
+     */
+    @NonNull
+    public List<OffloadServiceInfoWrapper> getAllInterfaceOffloadServiceInfos(
+            @NonNull String interfaceName) {
+        return mInterfaceOffloadServices.getOrDefault(interfaceName, Collections.emptyList());
+    }
+
     private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
             new MdnsInterfaceAdvertiser.Callback() {
         @Override
@@ -385,9 +397,12 @@
         }
     }
 
-    private static class OffloadServiceInfoWrapper {
-        private final @NonNull OffloadServiceInfo mOffloadServiceInfo;
-        private final int mServiceId;
+    /**
+     * The wrapper class for OffloadServiceInfo including the serviceId.
+     */
+    public static class OffloadServiceInfoWrapper {
+        public final @NonNull OffloadServiceInfo mOffloadServiceInfo;
+        public final int mServiceId;
 
         OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
             mOffloadServiceInfo = offloadServiceInfo;
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 49a6ef1..508e924 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -163,6 +163,7 @@
 
     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+    private val serviceName2 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
     private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
     private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
     private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -890,10 +891,11 @@
         }
     }
 
-    fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) {
-        assertEquals(serviceName, serviceInfo.key.serviceName)
-        assertEquals(serviceType, serviceInfo.key.serviceType)
-        assertEquals(listOf<String>("_subtype"), serviceInfo.subtypes)
+    fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo, si: NsdServiceInfo) {
+        val expectedServiceType = si.serviceType.split(",")[0]
+        assertEquals(si.serviceName, serviceInfo.key.serviceName)
+        assertEquals(expectedServiceType, serviceInfo.key.serviceType)
+        assertEquals(listOf("_subtype"), serviceInfo.subtypes)
         assertTrue(serviceInfo.hostname.startsWith("Android_"))
         assertTrue(serviceInfo.hostname.endsWith("local"))
         assertEquals(0, serviceInfo.priority)
@@ -907,36 +909,63 @@
         // The offload callbacks are only supported with the new backend,
         // enabled with target SDK U+.
         assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+
+        // TODO: also have a test that use an executor that runs in a different thread, and pass
+        // in the thread ID NsdServiceInfo to check it
+        val si1 = NsdServiceInfo()
+        si1.serviceType = "$serviceType,_subtype"
+        si1.serviceName = serviceName
+        si1.network = testNetwork1.network
+        si1.port = 23456
+        val record1 = NsdRegistrationRecord()
+
+        val si2 = NsdServiceInfo()
+        si2.serviceType = "$serviceType,_subtype"
+        si2.serviceName = serviceName2
+        si2.network = testNetwork1.network
+        si2.port = 12345
+        val record2 = NsdRegistrationRecord()
         val offloadEngine = TestNsdOffloadEngine()
-        runAsShell(NETWORK_SETTINGS) {
-            nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
-                OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
-                OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
-                { it.run() }, offloadEngine)
-        }
 
-        val si = NsdServiceInfo()
-        si.serviceType = "$serviceType,_subtype"
-        si.serviceName = serviceName
-        si.network = testNetwork1.network
-        si.port = 12345
-        val record = NsdRegistrationRecord()
-        nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
-        val addOrUpdateEvent = offloadEngine
-            .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
-                it.info.key.serviceName == serviceName
+        tryTest {
+            // Register service before the OffloadEngine is registered.
+            nsdManager.registerService(si1, NsdManager.PROTOCOL_DNS_SD, record1)
+            record1.expectCallback<ServiceRegistered>()
+            runAsShell(NETWORK_SETTINGS) {
+                nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
+                    OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
+                    OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
+                    { it.run() }, offloadEngine)
             }
-        checkOffloadServiceInfo(addOrUpdateEvent.info)
+            val addOrUpdateEvent1 = offloadEngine
+                .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+                    it.info.key.serviceName == si1.serviceName
+                }
+            checkOffloadServiceInfo(addOrUpdateEvent1.info, si1)
 
-        nsdManager.unregisterService(record)
-        val unregisterEvent = offloadEngine
-            .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
-                it.info.key.serviceName == serviceName
+            // Register service after OffloadEngine is registered.
+            nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, record2)
+            record2.expectCallback<ServiceRegistered>()
+            val addOrUpdateEvent2 = offloadEngine
+                .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+                    it.info.key.serviceName == si2.serviceName
+                }
+            checkOffloadServiceInfo(addOrUpdateEvent2.info, si2)
+
+            nsdManager.unregisterService(record2)
+            record2.expectCallback<ServiceUnregistered>()
+            val unregisterEvent = offloadEngine
+                .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
+                    it.info.key.serviceName == si2.serviceName
+                }
+            checkOffloadServiceInfo(unregisterEvent.info, si2)
+        } cleanupStep {
+            runAsShell(NETWORK_SETTINGS) {
+                nsdManager.unregisterOffloadEngine(offloadEngine)
             }
-        checkOffloadServiceInfo(unregisterEvent.info)
-
-        runAsShell(NETWORK_SETTINGS) {
-            nsdManager.unregisterOffloadEngine(offloadEngine)
+        } cleanup {
+            nsdManager.unregisterService(record1)
+            record1.expectCallback<ServiceUnregistered>()
         }
     }